四SpringSecurity重要代理类--DelegatingFilterProxyFilterChainProxy
四 SpringSecurity重要代理类--DelegatingFilterProxy/FilterChainProxy
4.1 DelegatingFilterProxy---由springWeb提供
基于Servlet规范,我们可以为 Servlet容器 注入一些自定义Filters,但是在 Spring 应用中,实现了Filter接口的Bean无法被 Servlet容器 感知到,没有调用ServletContext#addFilter方法注册到FilterChain中;
Spring 提供了一个DelegatingFilterProxy代理类,DelegatingFilterProxy实现了Filter,因此它可以被注入到FilterChain中,同时,当请求到来时,它会把请求转发到Spring容器 中实现了Filter接口的 Bean 实体,所以DelegatingFilterProxy桥接了 Servlet容器 和 Spring容器。
4.2 FilterChainProxy---springSecurity入口(保存List)
(http://www.javaboy.org/2020/0720/springsecurity-filterchainproxy.html)
4.2.1 执行流程
Spring容器中的Filters Bean实体可以注入到Servlet容器中的FilterChain,基于此,Spring Security向Spring容器提供了一个FilterChainProxy Bean 实体,该FilterChainProxy实现了Filter接口,因此,请求就会被FilterChainProxy捕获到,这样 Spring Security 就可以开始工作了。
默认情况下,DelegatingFilterProxy从Spring容器中获取得到的就是FilterChainProxy实体,而FilterChainProxy也是一个代理类,它最终会将请求转发到 Spring Security 提供的SecurityFilterChain中,web项目中,流程示意图如下所示:
注意:
FilterChainProxy就是 Spring Security 真正的入口起始点,调式代码时,将断点设置在FilterChainProxy#doFilter就可以追踪 Spring Security 完整调用流程。
可以看到,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 中这些功能到底是怎么实现的。
4.2.2 FilterChainProxy源码分析--拦截请求到security的filterChain
public class FilterChainProxy extends GenericFilterBean {
//FILTER_APPLIED 变量是一个标记,用来标记过滤器是否已经执行过了
private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
//filterChains 是过滤器链,注意,这个是过滤器链,而不是一个个的过滤器
private List<SecurityFilterChain> filterChains;
//filterChainValidator 是 FilterChainProxy 配置完成后的校验方法,默认使用的 NullFilterChainValidator 实际上对应了一个空方法,也就是不做任何校验
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
//FilterChain为servlet的过滤器链
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//为true,表示尚未执行过;false表示,已经标记
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//执行spring security的过滤器
doFilterInternal(request, response, chain);
}
finally {
//清除SecurityContextHolder 中保存的用户信息,同时移除 request 中的标记
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
在 doFilter 方法中,正常来说,clearContext 参数每次都是 true,于是每次都先给 request 标记上 FILTER_APPLIED 属性,然后执行 doFilterInternal 方法去走过滤器,执行完毕后,最后在 finally 代码块中清除 SecurityContextHolder 中保存的用户信息,同时移除 request 中的标记。
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
//根据当前request,从FilterChainProxy中配置的多个过滤器链(可以只有一个),找到和当前相匹配的一个securityFilterChain,并获取filters
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
//如果filters为null,表示当前请求不需要执行security的过滤器链,直接执行servlet上的过滤器链
chain.doFilter(fwRequest, fwResponse);
return;
}
//如果需要执行security的过滤器链,首先构造一个虚拟的过滤器链
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
//request匹配到的过滤器链上的所有filters
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
doFilterInternal 方法就比较重要了:
- 首先将请求封装为一个 FirewalledRequest 对象,在这个封装的过程中,也会判断请求是否合法。
- 对响应进行封装。
- 调用 getFilters 方法找到过滤器链。该方法就是根据当前的请求,从 filterChains 中找到对应的过滤器链,然后由该过滤器链去处理请求。
- 如果找出来的 filters 为 null,或者集合中没有元素,那就是说明当前请求不需要经过过滤器。直接执行 chain.doFilter ,这个就又回到原生过滤器中去了。那么什么时候会发生这种情况呢?那就是针对项目中的静态资源,如果我们配置了资源放行,如
web.ignoring().antMatchers("/hello");
,那么当你请求 /hello 接口时就会走到这里来,也就是说这个不经过 Spring Security Filter。 - 如果查询到的 filters 中是有值的,那么这个 filters 集合中存放的就是我们要经过的过滤器链了。此时它会构造出一个虚拟的过滤器链 VirtualFilterChain 出来,并执行其中的 doFilter 方法。
private static class VirtualFilterChain implements FilterChain {
//servlet的原生过滤器链
private final FilterChain originalChain;
//request匹配到的过滤器链上的所有filters
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
//request中匹配到的filter个数
private final int size;
//filters处理的当前位置
private int currentPosition = 0;
private VirtualFilterChain(FirewalledRequest firewalledRequest,
FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
this.firewalledRequest = firewalledRequest;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
//表示当前filters上过滤器处理完毕
if (currentPosition == size) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
//执行完security的过滤器,返回执行servlet的过滤器链
originalChain.doFilter(request, response);
}
else {
currentPosition++;
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
//依次执行security的过滤器
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 接口及其实现:
public interface FilterChainValidator {
void validate(FilterChainProxy filterChainProxy);
}
private static class NullFilterChainValidator implements FilterChainValidator {
@Override
public void validate(FilterChainProxy filterChainProxy) {
}
}
实际上这个实现并未做任何事情。