再探过滤器链
在 概述(一):SpringSecurity的前世今生 中我们曾提过Spring Security底层是通过一组过滤器链来实现的,当时简单的介绍了几个比较重要的过滤器;并且在后面的几篇认证文章中都有比较详细的讲解。
但前几篇的系列文章更多的是关注 认证
本身,对一些细枝末节没有额外的展开;而本篇文章的目的是再探 Spring Security 的过滤器链,完整的剖析全链路。
经过 认证(一):基于表单登录的认证模式 的介绍,我们对认证的流程有了一个比较全面的认识;但是对于认证之后的事情呢?比如说:为什么我登录之后就不用再登录了呢?登录成功之后用户信息保存在哪里呢…… 别急,让我们先来看看完整的过滤器链以及认证链路全流程。
从过滤器链图中我们发现,当我们在请求资源的时候,请求会先经过 SecurityContextPersistenceFilter
过滤器,从其名字中我们大概能猜测出它应该是与某些资源的持久化(Persistence)有关。
认证链路全流程图大致上可以分两部分来理解,一部分是荧光绿的认证流程(在之前的认证系列中已经有过比较详细的将讲解);而另一部分是认证后的响应流程。
揭秘之旅
结合两张图示来看,未知的接口/类主要有:Authentication
、SecurityContext
、 SecurityContextHolder
,SecurityContextPersistenceFilter
、 那么就让我们一起来揭开它们神秘的面纱吧!
Authenticaiton
Authentication
接口之前曾有过介绍,在认证成功之前存储的是客户端传递的登录信息,认证成功之后存储的是用户信息。
public interface Authentication extends Principal, Serializable {
/**
* 权限相关信息
* 因SecurityContext存储的Authentication对象的经过认证的,所以会带有权限信息
*/
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
/**
* 是否认证
* 认证前存储的是客户端传递的登录信息,认证后存储的是用户信息
*/
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
SecurityContext
SecurityContext
用以存储认证授权的相关信息(已认证的Authentication),换句话说就是存储 当前用户
的账号信息以及相关权限信息。
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
SecurityContextHolder
前面讲到 SecurityContext
存储的是 当前用户
的相关信息,那么如何判定当前用户
是谁呢?实际上一个请求从开发到结束一般会是一个线程来处理,所以这段时间内这个 当前用户
是和当前的处理线程是一一对应的。 SecurityContextHolder
类的作用就是将 SecurityContext
和当前线程所绑定,将其存储到当前线程中(实现方式是利用 ThreadLocal
)。
SecurityContextHolder
主要是用于框架内部使用,比如说利用它获取当前用户的 SecurityContext
进行请求检查以及访问控制等。
这时候可能有朋友会有疑问:“既然是与当前线程所绑定,那么当一个请求结束后,用户信息不就消失了吗?那不是每次都会需要重新认证?”
答案在 SecurityContextPersistenceFilter
中。
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter
是过滤器链中的第一个过滤器,它的主要工作是:当请求来临的时候,它会从 HttpSession
中把对应用户的 SecurityContext
放入到 SecurityContextHolder
并且在所有拦截器都处理完毕之后,将含有当前用户信息的 SecurityContext
存入 HttpSession
中,并清除 SecurityContextHolder
中的引用(释放 ThreadLoacal
)。
public class SecurityContextPersistenceFilter extends GenericFilterBean {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
private SecurityContextRepository repo;
private boolean forceEagerSessionCreation;
public SecurityContextPersistenceFilter() {
this(new HttpSessionSecurityContextRepository());
}
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
this.forceEagerSessionCreation = false;
this.repo = repo;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (request.getAttribute("__spring_security_scpf_applied") != null) {
chain.doFilter(request, response);
} else {
boolean debug = this.logger.isDebugEnabled();
request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
if (this.forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
this.logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
// 从HttpSessionSecurityContextRepository中尝试获取SecurityContext对象,如果是还未登录的用户,则返回的是一个空的SecurityContext对象
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
boolean var13 = false;
try {
var13 = true;
// 将securityContext设置到 SecurityContextHolder中
SecurityContextHolder.setContext(contextBeforeChainExecution);
// 执行后续的拦截器链
chain.doFilter(holder.getRequest(), holder.getResponse());
var13 = false;
} finally {
if (var13) {
// 当后续的拦截器链执行完毕后,从SecurityContextHolder中取出 securityContext并做如下事情
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// 1.回收SecurityContextHolder中的 securityContext引用(相当于释放ThreadLocal)
SecurityContextHolder.clearContext();
// 2.将 securityContext信息存储到HttpSession中
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
}
}
总结
至此,我们总算对认证的全链路有了一个比较完整的认识:当请求到达服务端,首先会经过 SecurityContextPersistenceFilter
拦截器,它会尝试从 HttpSession
中获取安全上下文 SecurityContext
并且将其设置到 SecurityContextHolder
中,然后等过滤器链上的所有过滤器都执行完后,再将 SecurityContextHolder
中的 安全上下文 SecurityContext
清除(请求走到了最后阶段),并且将 SecurityContext
设置到 HttpSession
中。
HttpSession
存在于服务端,默认的过期时间是30分钟(当然这些都是可以设置的),有兴趣的朋友可以自行了解一番~
本文为个人学习总结,如有错误之处请指出!
参考:Spring Security 之 SecurityContext
文章到这就结束拉,欢迎大家扫码关注小奇公众号~