深入理解 FilterChainProxy【源码篇】

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 源码-全局属性

private static final Log logger = LogFactory.getLog(FilterChainProxy.class);

private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");

private List<SecurityFilterChain> filterChains;

private FilterChainValidator filterChainValidator = new NullFilterChainValidator();

private HttpFirewall firewall = new StrictHttpFirewall();

private RequestRejectedHandler requestRejectedHandler = new DefaultRequestRejectedHandler();

  • FILTER_APPLIED 变量是一个标记,用来标记过滤器是否已经执行过了。这个标记在 Spring Security 中很常见,这里就不多说了。
  • filterChains 是过滤器链,注意,这个是过滤器链,而不是一个个的过滤器,配置多个过滤器链时,配置的多个过滤器链就保存在 filterChains 变量中,也就是,如果你有一个过滤器链,这个集合中就保存一条记录,你有两个过滤器链,这个记录中就保存两条记录,每一条记录又对应了过滤器链中的一个个过滤器。
  • filterChainValidator 是 FilterChainProxy 配置完成后的校验方法,默认使用的 NullFilterChainValidator 实际上对应了一个空方法,也就是不做任何校验。
  • firewall 是 Spring Security 自带防火墙!
  • requestRejectedHandler 是异常处理器,默认就是抛出 RequestRejectedException 异常,可以替换默认实现!

doFilter 方法

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
	if (!clearContext) {
		doFilterInternal(request, response, chain);
		return;
	}
	try {
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		doFilterInternal(request, response, chain);
	}
	catch (RequestRejectedException ex) {
		this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
	}
	finally {
		SecurityContextHolder.clearContext();
		request.removeAttribute(FILTER_APPLIED);
	}
}

doFilterInternal 方法

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
	HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
	List<Filter> filters = getFilters(firewallRequest);
	if (filters == null || filters.size() == 0) {
		if (logger.isTraceEnabled()) {
			logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
		}
		firewallRequest.reset();
		chain.doFilter(firewallRequest, firewallResponse);
		return;
	}
	if (logger.isDebugEnabled()) {
		logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
	}
	VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
	virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}

doFilterInternal 方法就比较重要了:

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

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

VirtualFilterChain

private static final class VirtualFilterChain implements FilterChain {

	private final FilterChain originalChain;

	private final List<Filter> additionalFilters;

	private final FirewalledRequest firewalledRequest;

	private final int size;

	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 {
		if (this.currentPosition == this.size) {
			if (logger.isDebugEnabled()) {
				logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
			}
			// Deactivate path stripping as we exit the security filter chain
			this.firewalledRequest.reset();
			this.originalChain.doFilter(request, response);
			return;
		}
		this.currentPosition++;
		Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
		if (logger.isTraceEnabled()) {
			logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
					this.currentPosition, this.size));
		}
		nextFilter.doFilter(request, response, this);
	}

}
  1. VirtualFilterChain 类中首先声明了 5 个全局属性,originalChain 表示原生的过滤器链,也就是 Web Filter;additionalFilters 表示 Spring Security 中的过滤器链;firewalledRequest 表示当前请求;size 表示过滤器链中过滤器的个数;currentPosition 则是过滤器链遍历时候的下标。
  2. doFilter 方法就是 Spring Security 中过滤器挨个执行的过程,如果 currentPosition == size,表示过滤器链已经执行完毕,此时通过调用 originalChain.doFilter 进入到原生过滤链方法中,同时也退出了 Spring Security 过滤器链。否则就从 additionalFilters 取出 Spring Security 过滤器链中的一个个过滤器,挨个调用 doFilter 方法【没使用循环结构,为什么能挨个调用?可以看到第三个传参将当前对象(VirtualFilterChain )传了进去。后续在每个filter 执行完毕后,都有调用该对象的 doFilter 方法,递归结构!】。

最后

FilterChainProxy 中还定义了 FilterChainValidator 接口及其实现:

public interface FilterChainValidator {

	void validate(FilterChainProxy filterChainProxy);

}

private static class NullFilterChainValidator implements FilterChainValidator {

	@Override
	public void validate(FilterChainProxy filterChainProxy) {
	}

}

可以看到什么事情也没做

RequestRejectedHandler

public interface RequestRejectedHandler {

	/**
	 * Handles an request rejected failure.
	 * @param request that resulted in an <code>RequestRejectedException</code>
	 * @param response so that the user agent can be advised of the failure
	 * @param requestRejectedException that caused the invocation
	 * @throws IOException in the event of an IOException
	 * @throws ServletException in the event of a ServletException
	 */
	void handle(HttpServletRequest request, HttpServletResponse response,
			RequestRejectedException requestRejectedException) throws IOException, ServletException;

}

public class DefaultRequestRejectedHandler implements RequestRejectedHandler {

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			RequestRejectedException requestRejectedException) throws IOException, ServletException {
		throw requestRejectedException;
	}

}

可以看到请求异常处理器,默认实现就是抛出 RequestRejectedException 异常【可以替换默认实现,FilterChainProxy 对象中有提供 setRequestRejectedHandler 方法】

引用

  1. 深入理解 FilterChainProxy【源码篇】
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值