but 松哥这个系列还没发完呢,在我的操持中,Spring Security 系列目前该当能更新一半,还剩一半,虽然有的小伙伴可能以为彷佛已经没啥了,实在还有很多东西。。。
松哥最近也是特殊忙,Security 更新慢下来了,但是秉持前面说的,要学就成系列的学,要学就学透彻,这个系列我还会连续更下去。
本日我们就来聊一聊 Spring Security 系列中的 FilterChainProxy。

这是一个非常主要的代理工具。
1. FilterChainProxy我们先来回顾一下前面文章讲的:
在一个 Web 项目中,要求流程大概如下图所示:
要求从客户端发起(例如浏览器),然后穿过层层 Filter,终极来到 Servlet 上,被 Servlet 所处理。
上图中的 Filter 我们可以称之为 Web Filter,Spring Security 中的 Filter 我们可以称之为 Security Filter,它们之间的关系如下图:
可以看到,Spring Security Filter 并不是直接嵌入到 Web Filter 中的,而是通过 FilterChainProxy 来统一管理 Spring Security Filter,FilterChainProxy 本身则通过 Spring 供应的 DelegatingFilterProxy 代理过滤器嵌入到 Web Filter 之中。
❝
DelegatingFilterProxy 很多小伙伴该当比较熟习,在 Spring 中手工致合 Spring Session、Shiro 等工具时都离不开它,现在用了 Spring Boot,很多事情 Spring Boot 帮我们做了,以是有时候会觉得 DelegatingFilterProxy 的存在感有所降落,实际上它一贯都在。
FilterChainProxy 中可以存在多个过滤器链,如下图:
可以看到,当要求到达 FilterChainProxy 之后,FilterChainProxy 会根据要求的路径,将要求转发到不同的 Spring Security Filters 上面去,不同的 Spring Security Filters 对应了不同的过滤器,也便是不同的要求将经由不同的过滤器。
这是 FilterChainProxy 的一个大致功能,本日我们就从源码上理解 FilterChainProxy 中这些功能到底是怎么实现的。
2. 源码剖析先把 FilterChainProxy 源码亮出来,这个源码比较上,我们一部分一部分来,先从它声明的全局属性上开始:
privatefinalstaticStringFILTER_APPLIED=FilterChainProxy.class.getName().concat(".APPLIED");privateList<SecurityFilterChain>filterChains;privateFilterChainValidatorfilterChainValidator=newNullFilterChainValidator();privateHttpFirewallfirewall=newStrictHttpFirewall();
FILTER_APPLIED 变量是一个标记,用来标记过滤器是否已经实行过了。这个标记在 Spring Security 中很常见,松哥这里就不多说了。filterChains 是过滤器链,把稳,这个是过滤器链,而不是一个个的过滤器,在【Spring Security 竟然可以同时存在多个过滤器链?】一文中,松哥教过大家如何配置多个过滤器链,配置的多个过滤器链就保存在 filterChains 变量中,也便是,如果你有一个过滤器链,这个凑集中就保存一条记录,你有两个过滤器链,这个记录中就保存两条记录,每一条记录又对应了过滤器链中的一个个过滤器。filterChainValidator 是 FilterChainProxy 配置完成后的校验方法,默认利用的 NullFilterChainValidator 实际上对应了一个空方法,也便是不做任何校验。firewall 我们在前面的文章中也先容过(Spring Security 自带防火墙!
你都不知道自己的系统有多安全!
),这里就不再赘述。
接下来我们来看一个过滤器中最主要的 doFilter 方法:
@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{booleanclearContext=request.getAttribute(FILTER_APPLIED)==null;if(clearContext){try{request.setAttribute(FILTER_APPLIED,Boolean.TRUE);doFilterInternal(request,response,chain);}finally{SecurityContextHolder.clearContext();request.removeAttribute(FILTER_APPLIED);}}else{doFilterInternal(request,response,chain);}}
在 doFilter 方法中,正常来说,clearContext 参数每次都是 true,于是每次都先给 request 标记上 FILTER_APPLIED 属性,然后实行 doFilterInternal 方法去走过滤器,实行完毕后,末了在 finally 代码块中打消 SecurityContextHolder 中保存的用户信息,同时移除 request 中的标记。
按着这个顺序,我们来看 doFilterInternal 方法:
privatevoiddoFilterInternal(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{FirewalledRequestfwRequest=firewall.getFirewalledRequest((HttpServletRequest)request);HttpServletResponsefwResponse=firewall.getFirewalledResponse((HttpServletResponse)response);List<Filter>filters=getFilters(fwRequest);if(filters==null||filters.size()==0){if(logger.isDebugEnabled()){logger.debug(UrlUtils.buildRequestUrl(fwRequest)+(filters==null?"hasnomatchingfilters":"hasanemptyfilterlist"));}fwRequest.reset();chain.doFilter(fwRequest,fwResponse);return;}VirtualFilterChainvfc=newVirtualFilterChain(fwRequest,chain,filters);vfc.doFilter(fwRequest,fwResponse);}privateList<Filter>getFilters(HttpServletRequestrequest){for(SecurityFilterChainchain:filterChains){if(chain.matches(request)){returnchain.getFilters();}}returnnull;}
doFilterInternal 方法就比较主要了:
首先将要求封装为一个 FirewalledRequest 工具,在这个封装的过程中,也会判断要求是否合法。详细参考松哥之前的 Spring Security 自带防火墙!你都不知道自己的系统有多安全!
一文。对相应进行封装。调用 getFilters 方法找到过滤器链。该方法便是根据当前的要求,从 filterChains 中找到对应的过滤器链,然后由该过滤器链去处理要求,详细可以参考 Spring Security 竟然可以同时存在多个过滤器链? 一文。如果找出来的 filters 为 null,或者凑集中没有元素,那便是解释当前要求不须要经由过滤器。直接实行 chain.doFilter ,这个就又回到原生过滤器中去了。那么什么时候会发生这种情形呢?那便是针对项目中的静态资源,如果我们配置了资源放行,如 web.ignoring().antMatchers("/hello");,那么当你要求 /hello 接口时就会走到这里来,也便是说这个不经由 Spring Security Filter。这个松哥之前也专门写文章先容过:Spring Security 两种资源放行策略,千万别用错了!
。如果查询到的 filters 中是有值的,那么这个 filters 凑集中存放的便是我们要经由的过滤器链了。此时它会布局出一个虚拟的过滤器链 VirtualFilterChain 出来,并实行个中的 doFilter 方法。
那么接下来我们就来看看 VirtualFilterChain:
privatestaticclassVirtualFilterChainimplementsFilterChain{privatefinalFilterChainoriginalChain;privatefinalList<Filter>additionalFilters;privatefinalFirewalledRequestfirewalledRequest;privatefinalintsize;privateintcurrentPosition=0;privateVirtualFilterChain(FirewalledRequestfirewalledRequest,FilterChainchain,List<Filter>additionalFilters){this.originalChain=chain;this.additionalFilters=additionalFilters;this.size=additionalFilters.size();this.firewalledRequest=firewalledRequest;}@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse)throwsIOException,ServletException{if(currentPosition==size){if(logger.isDebugEnabled()){logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)+"reachedendofadditionalfilterchain;proceedingwithoriginalchain");}//Deactivatepathstrippingasweexitthesecurityfilterchainthis.firewalledRequest.reset();originalChain.doFilter(request,response);}else{currentPosition++;FilternextFilter=additionalFilters.get(currentPosition-1);if(logger.isDebugEnabled()){logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)+"atposition"+currentPosition+"of"+size+"inadditionalfilterchain;firingFilter:'"+nextFilter.getClass().getSimpleName()+"'");}nextFilter.doFilter(request,response,this);}}}
VirtualFilterChain 类中首先声明了 5 个全局属性,originalChain 表示原生的过滤器链,也便是 Web Filter;additionalFilters 表示 Spring Security 中的过滤器链;firewalledRequest 表示当前要求;size 表示过滤器链中过滤器的个数;currentPosition 则是过滤器链遍历时候的下标。doFilter 方法便是 Spring Security 中过滤器挨个实行的过程,如果 currentPosition == size,表示过滤器链已经实行完毕,此时通过调用 originalChain.doFilter 进入到原生过滤链方法中,同时也退出了 Spring Security 过滤器链。否则就从 additionalFilters 取出 Spring Security 过滤器链中的一个个过滤器,挨个调用 doFilter 方法。
末了,FilterChainProxy 中还定义了 FilterChainValidator 接口及实在现:
publicinterfaceFilterChainValidator{voidvalidate(FilterChainProxyfilterChainProxy);}privatestaticclassNullFilterChainValidatorimplementsFilterChainValidator{@Overridepublicvoidvalidate(FilterChainProxyfilterChainProxy){}}
实际上这个实现并未做任何事情。
这便是 FilterChainProxy 中的全体逻辑。
3. 小结实在本文中的很多知识点松哥在之前的文章中都和大家先容过,例如配置多个过滤器链、StrictHttpFireWall、两种资源放行办法等等,但是之前紧张是和大家分享用法,没有去细究他的事理,本日的文章,我们通过源码把这个问题捋了一遍,相信大家对付这些功能细节也更清晰了。
好啦,感兴趣的小伙伴记得点个在看鼓励下松哥哦~