写在最前,本人也只是个大三的学生,如果你发现任何我写的不对的,请在评论中指出。
Spring Security过滤链
如何查看整条过滤链?
根据spring官网的说辞,为了能更好的了解spring security这个框架,官方更推荐我们从spring security的过滤链开始研究,所以我直接从官网扒了一张图开始研究:
首先,spring容器与servlet容器是两个不相关的概念,但spring security的servlet为了支持基于servlet的过滤器,因此设计了DelegatingFilterProxy
,可以在servlet容器的生命周期和spring的ApplicationContext
之间进行桥接。而图中的FilterChainProxy
则是spring security提供的用于获取过滤链的bean
, 它可以被DelegatingFilterProxy
从ApplicationContext
中获取。
那么接下来我们可以直接启动一个spring security demo来根据官方给出的几个代理类来查看主要流程,实际上我们可以直接在FilterChainProxy
的doFilter方法处打上断点,对于DelegatingFilterProxy
可以直接关注它的invokeDelegate
方法(这个方法才是真正的dofilter逻辑, 该方法前面的都是进行初始化)。
继续查看doFilter方法
再进入doFilterInternal方法,就可以浏览当前spring security的过滤链
继续跟进FilterChainProxy, 它还是实现了一个内部类VirtualFilterChain ,可以让过滤链集合中的过滤器工作
到这里,基本就是过滤器链开始执行各过滤器的核心过滤器方法,之后就是spring security认证通过。
过滤链介绍
记录一下目前编写的demo在addtionalFiliters
内的出现的部分filters。但在这之前需要提一下OncePerRequestFilter
,spring security部分的过滤器都继承了该类,该类的意旨在于每个请求仅执行一次(以解决各种容器不兼容的问题),其中doFilterInternal
留白给子类发挥空间。
- SecurityContextPersistenceFilter
该过滤类,在源码介绍的最前就摆明了自身的功能:可以从之前配置的SecurityContextRepository
中取得已经存储了信息的SecurityContextHodler
,并在请求完成之后清除原先的SecurityContextHodler
,将新的SecurityContextHodler
存储回SecurityContextHodler
,以供后续的过滤器使用。
// 直接看到dofilter方法内
// HttpRequestResponseHolder仅仅只是把request和response封装到了一起
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
// loadContext方法会判断当前的SecurityContext是否空,为空则new一个,不为空则保存到SaveToSessionResponseWrapper,供后续使用
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
// 之后就是将获取到的这个context通过set方法保留,然后根据之前context内的认证信息(null ? true : false)日志记录
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
if (contextBeforeChainExecution.getAuthentication() == null) {
logger.debug("Set SecurityContextHolder to empty SecurityContext");
}
else {
if (this.logger.isDebugEnabled()) {
this.logger
.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
}
}
chain.doFilter(holder.getRequest(), holder.getResponse());
}
// 之后就是开头所说,需要清理。请求结束后清除SecurityContextHolder中的SecurityContext的操作是必须的
// 因为默认情况下SecurityContextHolder会把SecurityContext存储到ThreadLocal中,而这个thread刚好是存在于servlet容器的线程池中的
// 如果不清除,当后续请求又从线程池中分到这个线程时,程序就会拿到错误的认证信息
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// Crucial removal of SecurityContextHolder contents before anything else.
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
this.logger.debug("Cleared SecurityContextHolder to complete request");
}
- HeaderWriterFilter
这个过滤类的话,源码也不多,文档解析也很清除明了,Spring Securty 使用该Filter在一个请求的处理过程中为响应对象增加一些头部信息。头部信息由外部提供,比如用于增加一些浏览器保护的头部,比如X-Frame-Options, X-XSS-Protection和X-Content-Type-Options等。
- LogoutFilter
登出过滤器,会轮询一系列的LogoutHandler
,这些处理器是注销的时候而调用。
// 直接上源码
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 判读是否需要退出,主要通过请求的url是否是filterProcessesUrl来识别
if (requiresLogout(request, response)) {
// 获取认证实例
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Logging out [%s]", auth));
}
this.handler.logout(request, response, auth);
// 退出成功后,进行redirect操作
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
一般而言,我们在SecurityContextPersistenceFilter
中只产生了一个空的SecurityContext
,这里应该是一个没有认证信息的SecurityContext
实例,得到的应该是Null吧?但注意这个是登出过滤器, 这条分支走到是登出时的信息, 所以此时存在实例信息的。需要注意的是后续的注销处理器, 至少有一个SecurityContextLogoutHandler
,如果有记住我这个服务,就还有一个处理器,不过它们的任务基本上都是:让session失效和清除SecurityContext实例。
- RequestCacheAwareFilter
该过滤链在源码中的介绍是一个已缓存的请求与当前请求匹配,则负责重构保存的请求,白话讲就是:用于用户登录成功后,重新恢复因为登录被打断的请求,就不多讲了(我不会)。
- SecurityContextHolderAwareRequestFilter
再一次看到源码介绍,简单粗暴的讲了该过滤器是用于包装ServletRequest
的。
// 源码看起来也异常的简单
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(this.requestFactory.create((HttpServletRequest) req, (HttpServletResponse) res), res);
}
这个过滤器看起来很简单。目的仅仅是实现java ee中servlet api一些接口方法。 一些应用中直接使用getRemoteUser方法、isUserInRole方法,在使用spring security时其实就是通过这个过滤器来实现的。
- AnonymousAuthenticationFilter
该过滤器理解起来也比较容易,因为很容易会在security config配置类里配置一些允许匿名访问的路径。实际上就是通过该过滤器实现的。它会手工设置了一个匿名的登录人Authentication。我们可以在其后的过滤器中,如果没有登录时,我们也可以知道当前访问的是匿名用户。
protected Authentication createAuthentication(HttpServletRequest request) {
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(this.key, this.principal,
this.authorities);
token.setDetails(this.authenticationDetailsSource.buildDetails(request));
return token;
}
- SessionManagementFilter
如果只看源码的话,它在dofilter
方法中从SecurityContext
中获取认证信息,并判断当前用户不是匿名用户,就将当前用户信息保存,并且通过securityContextRepository
保存当前SecurityContext
(想更详细的了解,可以通过下面两个链接):