深入理解 FilterChainProxy【源码篇】

news2024/12/23 16:36:34

目录

    • FilterChainProxy
    • 源码分析
      • FilterChainProxy 源码-全局属性
      • doFilter 方法
      • doFilterInternal 方法
      • VirtualFilterChain
    • 最后
      • RequestRejectedHandler
    • 引用

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【源码篇】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TechG年度科技行业盛会来袭,深兰科技展台亮点抢先看!

2022年12月29日——31日&#xff0c;科技行业盛会「上海国际消费电子技术展(TechG)将在南京举办&#xff0c;深兰科技机器人产业集团将携人工智能机器人产品“家族”亮相展会&#xff0c;全面展示先进的人工智能机器人技术&#xff0c;呈现安全、高效、新潮的机器人应用场景。 …

D. Lucky Chains edu139 div2

Problem - D - Codeforces 题意是给你a和b&#xff0c;要求__gcd(ak,bk)1的k最多可以增加多少个1 分析&#xff1a; 遇到这种的最大公约数的问题&#xff0c;有很大概率都是推公式&#xff0c;以及使用筛法去把所有的质数筛出来利用质因子去缩短时间 这题就是一个推公式的题…

【UE4 第一人称射击游戏】11-武器跟随鼠标移动并添加开火音效

上一篇&#xff1a; 【UE4 第一人称射击游戏】10-添加冲刺功能 本篇效果&#xff1a; 步骤&#xff1a; 1.打开“SWAT_AnimBP”&#xff0c;添加3个“变换&#xff08;修改&#xff09;骨骼”节点 选中第一个“变换&#xff08;修改&#xff09;骨骼”节点&#xff0c;在细节…

JAVA——把一批压缩文件中存放的部分数据进行处理(替换)

JAVA——把一批压缩文件中存放的部分数据进行处理&#xff08;替换&#xff09;一、需求二、分析三、具体实现1.解压压缩文件2.读取解压后的文件并且按照一定逻辑处理3.把文件压缩存储4.方法的调用5.需要添加的依赖四、执行结果五、用到的工具类六、可以改进的地方1.文件处理完…

OpenText Exceed TurboX 客户案例——SMS 集团

SMS 集团通过 OpenText 提高工程师的工作效率。OpenText Exceed TurboX 帮助该制造商提高生产力和可靠性、降低成本并实现全球协作。 SMS集团存在的挑战 需要一个可以在全球范围内轻松访问的解决方案&#xff1b;需要一个系统&#xff0c;能够无缝运行图形要求苛刻的基于服务…

Git知识型整理

目的 了解Git&#xff0c; 以及工作原理&#xff0c; 能更好让自己解决在仓库中遇到的各种问题。 git 什么是git 分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。很多版本管理工具如&#xff1a;sourcetree, gitLab, tortoise等等&#…

贵金属软件MT4好不好用?MT4软件有什么优势特点?

如今的贵金属市场还是比较大的&#xff0c;有许多投资者都希望可以在里面赚一分钱&#xff0c;其实在市场变得很大的时候&#xff0c;我们更应该小心一点。因为如今市场当中能够选择的软件实在是太多了&#xff0c;我们可以查看一下哪一个软件比较适合投资&#xff0c;在挑选的…

1、初识C语言---“hello world”

文章目录1、什么是C语言呢?2、第一个C语言程序3、数据类型4、变量与常量4.1变量的定义方法4.2变量的命名规则4.3变量的使用4.4变量的作用域与生命周期4.5常量5.字符串转义字符注释5.1字符串5.2转义字符5.3注释1、什么是C语言呢? C语言是一门面向过程的、抽象化的通用程序设计…

Spring Boot学习篇(二)

Spring Boot学习篇(二) 1.spring boot中的拦截器 1.1 在com包.zlz包下面创建interceptor包,包的目录结构如下所示 1.2 在interceptor包下面创建MyInterceptor1和MyInterceptor2类(便于测试顺序) 1.2.1 MyInterceptor1类 package com.zlz.interceptor;import org.springfram…

集合家庭作业

Homework01 按要求实现: (1)封装一个新闻类&#xff0c;包含标题和内容属性&#xff0c;提供get、set方法,重写toString方法&#xff0c;打印对象时只打印标题; (2)只提供一个带参数的构造器&#xff0c;实例化对象时&#xff0c;只初始化标题;并且实例化两个对象: 新闻一:…

【C数组】详解数组

数组前言一、一维数组的创建和初始化&#xff08;一&#xff09;数组的创建1.数组的概念和创建方式2.变长数组&#xff08;二&#xff09;数组的初始化&#xff08;三&#xff09;一维数组的使用&#xff08;四&#xff09;一维数组在内存中的存储二、二维数组的创建和初始化&a…

rabbitmq基础7——队列和消息过期时间设置、死信队列、延迟队列、优先级队列、回调队列、惰性队列

文章目录一、过期时间1.1 针对队列设置1.2 针对消息设置二、死信队列2.1 死信交换器2.2 死信队列原理2.3 延迟队列&#xff08;特殊用法&#xff09;三、优先级队列3.1 监控页面创建优先级队列3.2 监控页面创建优先级消息四、回调队列4.1 RPC的定义4.2 PRC工作机制4.3 监控页面…

java本地socket服务端暴露至公网访问【内网穿透】

Java 服务端demo环境 jdk1.8 框架:springbootmaven 开发工具:IDEA 在pom文件引入第三包封装的netty框架maven坐标 <dependency><groupId>io.github.fzdwx</groupId><artifactId>sky-http-springboot-starter</artifactId><version>0.…

叮叮当~~叮叮当~~|您有一份白玉兰酒店圣诞节豪礼等待抱走!

不知不觉2022年已经临近尾声 全国各地都已经放开了 相信有很多小伙伴在这一年 都没有好好的出来浪一浪 在即将到来的圣诞节 各地的圣诞玩乐都已经齐齐亮相 同时 白玉兰酒店客房 也已迎来了圣诞系列主题 是时候计划一场完美的圣诞之旅了 兰小姐为您准备了丰富多彩的圣…

【苹果家庭群发推送】软件安装Apple推送是一种基于IMESSAGE平台的新信息推送功效的营销软件

推荐内容IMESSGAE相关 作者推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者推荐内容3.日历推 *** 点击即可查看作者要求内容信息作者推荐…

DevOps-7:Jenkins API介绍

前言&#xff1a; 为什么要使用Jenkins的API&#xff1f; 我在使用Jenkins的过程中&#xff0c;觉得Jenkins的UI还是有不少问题的&#xff1a; UI性能差&#xff0c;尤其是有一些任务在构建中时&#xff0c;UI非常卡顿&#xff0c;等个十来秒都正常&#xff0c;极端时甚至会崩…

LeetCode 221. 最大正方形

LeetCode 221. 最大正方形 在一个由 0 和 1 组成的二维矩阵内&#xff0c;找到只包含 1 的最大正方形&#xff0c;并返回其面积。 示例 1&#xff1a; 输入&#xff1a;matrix [["1","0","1","0","0"],["1",&quo…

【OpenCV-Python】教程:8-1 图像去噪 Image Denoising

OpenCV Python 图像去噪 Image Denoising 【目标】 非局部均值去噪算法去除图像中的噪声。 cv2.fastNlMeansDenoising() , cv2.fastNlMeansDenoisingColored() etc. 【理论】 在前面的章节中&#xff0c;我们已经看到了许多图像平滑技术&#xff0c;如高斯模糊&#xff0c…

1、MYSQL基础(DDL DML DCL)

&#xff08;1&#xff09;关于字段修改 change和modify的异同&#xff1a; 同&#xff1a;可以修改表的定义 异&#xff1a;change需要写两次列名&#xff0c;不方便&#xff0c;但是change可以修改表名&#xff0c;modify不可 &#xff08;2&#xff09;多表更新&#xff…

店铺如何快速实现数字化管理?不妨参考一下管理系统

百数店铺管理系统主要是以门店管理为核心&#xff0c;该应用管理涵盖商品、订单、库存、客户、采购、财务、营销等功能体系&#xff0c;维度数据分析&#xff0c;智能指导门店经营&#xff0c;账目清晰一目了然&#xff0c;店铺经营更高效。 1、销售看板 该分析报表里通过销售…