1、架构

Spring Security中认证授权等功能都是基于过滤器实现的。
Spring Security过滤器架构(源码篇) - 图1
在一个web项目中,请求从客户端发起(例如浏览器),然后穿过层层Filter,最终来到Servlet上,被Servlet处理。
上图中的Filter我们称之为Web Filter,Spring Security中的Filter我们称之为Security Filter,关系图如下:
Spring Security过滤器架构(源码篇) - 图2
可以看到,Spring Security Filter0…n 并不是直接嵌入到 Web Filter 中的,而是通过FilterChainProxy来统一管理 Spring Security Filter0…n,FilterChainProxy本身则通过 Spring 提供的DelegatingFilterProxy代理过滤器嵌入到 Web Filter 之中。
FilterChainProxy可以存在多个过滤器链,如下图:
Spring Security过滤器架构(源码篇) - 图3
可以看到,当请求到达FilterChainProxy之后,FilterChainProxy会根据请求的路径,将请求转发到不同的SecurityFilterChain上面去,不同的SecurityFilterChain包含了不同的过滤器,也就是不同的请求会经过不同的过滤器。
🤔其中 Spring Security 给我们提供了哪些过滤器呢?默认情况下那些过滤器会被加载呢?

过滤器 过滤器作用 默认是否加载
ChannelProcessingFilter 过滤请求协议 HTTP 、HTTPS NO
WebAsyncManagerIntegrationFilter 将 WebAsyncManger 与 SpringSecurity 上下文进行集成 YES
SecurityContextPersistenceFilter 在处理请求之前,将安全信息加载到 SecurityContextHolder 中 YES
HeaderWriterFilter 处理头信息加入响应中 YES
CorsFilter 处理跨域问题 NO
CsrfFilter 处理 CSRF 攻击 YES
LogoutFilter 处理注销登录 YES
OAuth2AuthorizationRequestRedirectFilter 处理 OAuth2 认证重定向 NO
Saml2WebSsoAuthenticationRequestFilter 处理 SAML 认证 NO
X509AuthenticationFilter 处理 X509 认证 NO
AbstractPreAuthenticatedProcessingFilter 处理预认证问题 NO
CasAuthenticationFilter 处理 CAS 单点登录 NO
OAuth2LoginAuthenticationFilter 处理 OAuth2 认证 NO
Saml2WebSsoAuthenticationFilter 处理 SAML 认证 NO
UsernamePasswordAuthenticationFilter 处理表单登录 YES
OpenIDAuthenticationFilter 处理 OpenID 认证 NO
DefaultLoginPageGeneratingFilter 配置默认登录页面 YES
DefaultLogoutPageGeneratingFilter 配置默认注销页面 YES
ConcurrentSessionFilter 处理 Session 有效期 NO
DigestAuthenticationFilter 处理 HTTP 摘要认证 NO
BearerTokenAuthenticationFilter 处理 OAuth2 认证的 Access Token NO
BasicAuthenticationFilter 处理 HttpBasic 登录 YES
RequestCacheAwareFilter 处理请求缓存 YES
SecurityContextHolderAwareRequestFilter 包装原始请求 YES
JaasApiIntegrationFilter 处理 JAAS 认证 NO
RememberMeAuthenticationFilter 处理 RememberMe 登录 NO
AnonymousAuthenticationFilter 配置匿名认证 YES
OAuth2AuthorizationCodeGrantFilter 处理OAuth2认证中授权码 NO
SessionManagementFilter 处理 session 并发问题 YES
ExceptionTranslationFilter 处理认证/授权中的异常 YES
FilterSecurityInterceptor 处理授权相关 YES
SwitchUserFilter 处理账户切换 NO

2、源码分析

先把FilterChainProxy源码亮出来,我们一部分一部分来,先从它声明的全局属性上开始:

  1. private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(
  2. ".APPLIED");
  3. private List<SecurityFilterChain> filterChains;
  4. private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
  • FILTER_APPLIED 变量是一个标记,用来标记过滤器是否已经执行过了。
  • filterChains 是过滤器链集合,其中的泛型是过滤器链,而不是一个个过滤器,配置的多个过滤器链就保存在 filterChains 变量中。
  • filterChainValidator 是 FilterChainProxy配置完成后的校验方法,默认使用的 NullFilterChainValidator 实际上对应了一个空方法,也就是不做任何校验。

接下来我们看一个过滤器中最重要的doFilter方法:

  1. @Override
  2. public void doFilter(ServletRequest request, ServletResponse response,
  3. FilterChain chain) throws IOException, ServletException {
  4. boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
  5. if (clearContext) {
  6. try {
  7. request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
  8. doFilterInternal(request, response, chain);
  9. }
  10. finally {
  11. SecurityContextHolder.clearContext();
  12. request.removeAttribute(FILTER_APPLIED);
  13. }
  14. }
  15. else {
  16. doFilterInternal(request, response, chain);
  17. }
  18. }

doFilter方法中,正常来说,clearContext 参数每次都应该为 true,于是每次都先给 request 标记上 FILTER_APPLIED 属性,然后执行doFilterInternal方法去走过滤器,执行完毕后,最后在 finally 代码块中清除SecurityContextHolder中保存的用户信息,同时移除 request 中的标记。
按着这个顺序,我们来看看doFilterInternal方法:

  1. private void doFilterInternal(ServletRequest request, ServletResponse response,
  2. FilterChain chain) throws IOException, ServletException {
  3. FirewalledRequest fwRequest = firewall
  4. .getFirewalledRequest((HttpServletRequest) request);
  5. HttpServletResponse fwResponse = firewall
  6. .getFirewalledResponse((HttpServletResponse) response);
  7. List<Filter> filters = getFilters(fwRequest);
  8. if (filters == null || filters.size() == 0) {
  9. if (logger.isDebugEnabled()) {
  10. logger.debug(UrlUtils.buildRequestUrl(fwRequest)
  11. + (filters == null ? " has no matching filters"
  12. : " has an empty filter list"));
  13. }
  14. fwRequest.reset();
  15. chain.doFilter(fwRequest, fwResponse);
  16. return;
  17. }
  18. VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
  19. vfc.doFilter(fwRequest, fwResponse);
  20. }
  21. private List<Filter> getFilters(HttpServletRequest request) {
  22. for (SecurityFilterChain chain : filterChains) {
  23. if (chain.matches(request)) {
  24. return chain.getFilters();
  25. }
  26. }
  27. return null;
  28. }

doFilterInternal方法就比较重要了!

  1. 首先将请求封装成一个FirewalledRequest对象,在这个封装的过程中,也会判断请求是否合法。
  2. 对响应进行封装。
  3. 调用getFilters()方法找到过滤器链。该方法就是根据当前的请求,从 filterChains 中找到对应的过滤器链,然后由该过滤器链去处理请求。
  4. 如果找出来的 filters 为 null,或者集合中没有元素,那就是说明当前请求不需要经过过滤器。直接执行chain.doFilter,这个时候就又回到原生过滤器中去了。那么什么时候会发生这种情况呢?那就是针对项目中的静态资源,如果我们配置了资源放行,如web.ignoring().antMatchers("/hello"),那么当你请求 /hello 接口时就会走到这里来,也就是说不经过 Spring Security Filters。
  5. 如果查询到的 filters 是有值的,那么这个 filters 集合中存放的就是我们要经过的过滤器链了。此时它会构造出一个虚拟的过滤器链VirtualFilterChain出来,并执行其中的doFilter方法。

那么接下来我们就来看看VirtualFilterChain

  1. private static class VirtualFilterChain implements FilterChain {
  2. private final FilterChain originalChain;
  3. private final List<Filter> additionalFilters;
  4. private final FirewalledRequest firewalledRequest;
  5. private final int size;
  6. private int currentPosition = 0;
  7. private VirtualFilterChain(FirewalledRequest firewalledRequest,
  8. FilterChain chain, List<Filter> additionalFilters) {
  9. this.originalChain = chain;
  10. this.additionalFilters = additionalFilters;
  11. this.size = additionalFilters.size();
  12. this.firewalledRequest = firewalledRequest;
  13. }
  14. @Override
  15. public void doFilter(ServletRequest request, ServletResponse response)
  16. throws IOException, ServletException {
  17. if (currentPosition == size) {
  18. if (logger.isDebugEnabled()) {
  19. logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
  20. + " reached end of additional filter chain; proceeding with original chain");
  21. }
  22. // Deactivate path stripping as we exit the security filter chain
  23. this.firewalledRequest.reset();
  24. originalChain.doFilter(request, response);
  25. }
  26. else {
  27. currentPosition++;
  28. Filter nextFilter = additionalFilters.get(currentPosition - 1);
  29. if (logger.isDebugEnabled()) {
  30. logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
  31. + " at position " + currentPosition + " of " + size
  32. + " in additional filter chain; firing Filter: '"
  33. + nextFilter.getClass().getSimpleName() + "'");
  34. }
  35. nextFilter.doFilter(request, response, this);
  36. }
  37. }
  38. }
  1. VirtualFilterChain类中首先声明了5个全局属性,originalChain 表示原生的过滤器链,也就是 Web Filter;additionalFilters 表示 Spring Security 中的过滤器链;firewalledRequest 表示当前请求;size 表示 Spring Security 中的过滤器链中过滤器的个数;currentPosition 则是过滤器链遍历时候的下标。
  2. doFilter方法就是 Spring Security 中过滤器挨个执行的过程,如果currentPosition == size,表示过滤器链已经执行完毕,此时通过调用originalChain.doFilter进入到原生过滤器链方法中,同时退出了 Spring Security 过滤器链。否则就从 additionalFilters 取出 Spring Security 过滤器链中的一个个过滤器,挨个调用 doFilter 方法。