SpringSecurity源码学习一:过滤器执行原理

news2024/10/6 6:41:29

目录

  • 1. web过滤器Filter
    • 1.1 filter核心类
    • 1.2 GenericFilterBean
    • 1.3 DelegatingFilterProxy
      • 1.3.1 原理
      • 1.3.2 DelegatingFilterProxy源码
  • 2. FilterChainProxy源码学习
    • 2.1 源码
      • 2.1.1 doFilterInternal方法源码
        • 2.1.1.1 getFilters()方法源码
        • 2.1.1.2 VirtualFilterChain方法源码
  • 3. 总结

在Spring Security源代码中,过滤器是用于处理安全相关任务的关键组件之一。它们用于在请求到达应用程序之前或之后执行特定的安全操作。

Spring Security中的过滤器链是一个由多个过滤器组成的链式结构。每个过滤器都负责执行特定的安全任务,并将请求传递给下一个过滤器。这些过滤器一起协同工作,以实现身份验证、授权、会话管理等安全功能。

在Spring Security源码中,有一个关键的过滤器叫做 FilterChainProxy ,它是整个过滤器链的入口点。 FilterChainProxy 负责协调和执行所有其他过滤器,并确保请求按照正确的顺序通过它们。 也就是我们自定义的过滤器是通过 FilterChainProxy插入到web过滤器链中的

在这里插入图片描述
一般过滤器链路
在这里插入图片描述

使用Spring Security的过滤器链路

1. web过滤器Filter

Filter就是过滤器,在JavaWeb体系中,他位于服务端,卡在请求/响应与Servlet之间做一些操作。通过过滤器我们可以做很多操作,例如:

  1. 认证和授权:Spring Security框架中的过滤器可以用于对用户进行身份验证和授权,以保护Web应用程序的安全性。

  2. 日志记录:可以使用过滤器记录HTTP请求和响应的详细信息,以便进行故障排除和性能优化。

  3. 缓存:可以使用过滤器实现缓存机制,以提高Web应用程序的性能和响应速度。

  4. 压缩:可以使用过滤器对响应进行压缩,以减少数据传输量,提高Web应用程序的性能。

  5. 字符编码:可以使用过滤器对请求和响应进行字符编码,以确保正确的数据传输和显示。

1.1 filter核心类

在Spring Web中,实现过滤器的核心类是 javax.servlet.Filter 。这是Java Servlet API中定义的标准接口。要在Spring Web中创建过滤器,通常需要创建一个实现 Filter 接口的类,并重写其方法,例如 doFilter() 。
以下是与Spring Web过滤器相关的一些核心类:

  1. OncePerRequestFilter :这是Spring提供的一个抽象类,扩展了 GenericFilterBean 。它确保每个请求只调用一次过滤器的 doFilter() 方法。
  2. GenericFilterBean :这是一个方便的基类,用于创建过滤器。它实现了 Filter 接口,并提供了额外的功能,如bean生命周期管理。
  3. DelegatingFilterProxy :这是一个Spring特定的类,它将过滤器功能委托给Spring应用程序上下文中定义的目标bean。通常在想要将过滤器应用于特定URL或向过滤器注入依赖项时使用。
    这些核心类有助于在Spring Web应用程序中创建和配置过滤器,允许您根据需求拦截和处理HTTP请求和响应。

1.2 GenericFilterBean

GenericFilterBean 是Spring Web框架提供的一个方便的基类,用于创建过滤器。它实现了 Filter 接口,并提供了一些额外的功能,例如bean生命周期管理和依赖注入。 GenericFilterBean 是自带init方法的,我们继承后只需要重写doFilter方法。
直接继承 GenericFilterBean 可以带来以下好处:

  1. 简化过滤器的创建:继承 GenericFilterBean 可以减少编写过滤器所需的代码量。它提供了一些默认的实现和方法,使得创建过滤器变得简单和方便。

  2. 生命周期管理: GenericFilterBean 实现了 Filter 接口,并提供了生命周期管理的功能。它可以在过滤器的初始化和销毁过程中执行相应的操作,例如资源的初始化和释放。

  3. 依赖注入: GenericFilterBean 支持依赖注入,您可以使用 @Autowired 注解将其他Spring bean注入到过滤器中。这使得在过滤器中使用其他组件、服务或依赖变得更加容易。

  4. 配置灵活性:通过继承 GenericFilterBean ,您可以根据需要自定义和配置过滤器的行为。您可以重写 doFilter() 方法以实现自定义的请求和响应处理逻辑,并使用其他提供的方法来访问过滤器的配置和上下文信息。

1.3 DelegatingFilterProxy

上边我们讲了GenericFilterBean,我们可以通过继承GenericFilterBean编写过滤器。此时的自定义的过滤器是交由Spring容器管理的,并不属于web的Servlet 容器管理。因此可以将过滤器分类两类,第一类是由 Web 容器进行管理,第二类是由 Spring 进行管理的,DelegatingFilterProxy 就是连接二者的桥梁。

1.3.1 原理

DelegatingFilterProxy是Spring框架提供的一个过滤器代理类,它可以将请求转发到一个或多个过滤器。它的原理是将一个实现了javax.servlet.Filter接口的Bean转换为Servlet过滤器,并将这些过滤器应用到Web应用程序中。
在Spring中,DelegatingFilterProxy通常与Spring的IoC容器结合使用。当一个请求到达Web应用程序时,Servlet容器会将请求传递给DelegatingFilterProxy。DelegatingFilterProxy会从Spring的IoC容器中获取目标过滤器的实例,并调用它的doFilter方法来处理请求。

1.3.2 DelegatingFilterProxy源码

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// Lazily initialize the delegate if necessary.
		//这个时候经过初始化,filter已经是spring容器中的bean,在spring-Security中,此时delegateToUse就是spring-Security的FilterChainProxy
		Filter delegateToUse = this.delegate;
		if (delegateToUse == null) {
			synchronized (this.delegateMonitor) {
				delegateToUse = this.delegate;
				if (delegateToUse == null) {
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: " +
								"no ContextLoaderListener or DispatcherServlet registered?");
					}
					delegateToUse = initDelegate(wac);
				}
				this.delegate = delegateToUse;
			}
		}

		// Let the delegate perform the actual doFilter operation.
		//代理模式 让代理Filter执行实际的doFilter方法,在spring-Security中,此时就是执行spring-Security的FilterChainProxy
		invokeDelegate(delegateToUse, request, response, filterChain);
	}

在源码中我们可以看到,使用了代理模式执行了被代理的过滤器的过滤方法。

2. FilterChainProxy源码学习

在Spring Security中,FilterChainProxy是一个核心的过滤器,它负责处理Web请求的安全过滤链。它的作用是将请求传递给一系列的安全过滤器,以便进行身份验证、授权、会话管理等安全相关的操作。

FilterChainProxy的主要功能是根据配置的安全过滤器链来处理请求。在Spring Security的配置中,可以定义多个过滤器,并指定它们的顺序和应用于特定URL模式的条件。当一个请求到达应用程序时,FilterChainProxy会按照配置的顺序依次调用这些过滤器,直到找到适用于请求的过滤器链。

每个过滤器链都包含一系列的过滤器,它们按照特定的顺序执行。这些过滤器可以执行不同的安全操作,例如身份验证过滤器用于验证用户的身份,授权过滤器用于检查用户是否具有访问权限,会话管理过滤器用于管理用户的会话等。

通过FilterChainProxy,Spring Security提供了一种灵活的方式来定义和管理安全过滤器链。它可以根据应用程序的需求进行配置,并提供了强大的安全功能,以保护Web应用程序免受各种安全威胁。

2.1 源码

public class FilterChainProxy extends GenericFilterBean {

可以看到,FilterChainProxy是继承了GenericFilterBean,FilterChainProxy 也是一个过滤器。

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		//此处是对请求是否第一次经过这个过滤器的判断,在spring security中的过滤器中这种用法是相当常见的
		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 {
			//清除security上下文中的保存的Authentication对象
			SecurityContextHolder.clearContext();
			request.removeAttribute(FILTER_APPLIED);
		}
	}

这是FilterChainProxy的大概逻辑,细节点在doFilterInternal方法中。

2.1.1 doFilterInternal方法源码

	private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		//初始化request,对request进行一层包装,同时校验了一下请求的方式是否允许,以及请求地址中是否有非法字符
		FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
		//初始化response,对response进行了一层包装,若在response中添加header或cookie时,会对header和cookie的一些属性进行校验
		HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
		//SpringSecurity一般都会注册很多默认Filter,
		//注意这些Filter在容器启动时就初始化好了
		//获取spring security中第一个匹配请求地址的过滤器列表
		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是一个过滤器链FilterChain
		//将过滤器列表和servlet过滤器链包装成一个新的过滤器链 VirtualFilterChain 是FilterChainProxy的内部类
		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
		virtualFilterChain.doFilter(firewallRequest, firewallResponse);
	}

这段主要功能是:

  1. getFilters()方法,获取spring security中第一个匹配请求地址的过滤器列表
  2. 创建VirtualFilterChain过滤器链,执行所有过滤器。
2.1.1.1 getFilters()方法源码
	/**
	 * Returns the first filter chain matching the supplied URL.
	 * @param request the request to match
	 * @return an ordered array of Filters defining the filter chain
	 */
	private List<Filter> getFilters(HttpServletRequest request) {
		int count = 0;
		for (org.springframework.security.web.SecurityFilterChain chain : this.filterChains) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
						this.filterChains.size()));
			}
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}
		return null;
	}

根据请求地址匹配过滤器列表

2.1.1.2 VirtualFilterChain方法源码
		//VirtualFilterChain是一个过滤器链FilterChain
		//将过滤器列表和servlet过滤器链包装成一个新的过滤器链 VirtualFilterChain 是FilterChainProxy的内部类
		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
		virtualFilterChain.doFilter(firewallRequest, firewallResponse);
	/**
	 * Internal {@code FilterChain} implementation that is used to pass a request through
	 * the additional internal list of filters which match the request.
	 */
	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 {
			//如果没有下一个过滤器,则回到servlet过滤器链
			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));
			}
			//传入this,nextFilter中的会继续调用VirtualFilterChain.doFilter()直至所有过滤器执行结束或抛出异常
			nextFilter.doFilter(request, response, this);
		}

	}

VirtualFilterChain一个过滤器链,执行additionalFilters集合中的所有过滤器。 也可以把VirtualFilterChain当作SecurityFilterChain,additionalFilters集合中的元素就是 Security Filter,也就是我们自己写的过滤器。

3. 总结

在这里插入图片描述

在 上图中,一个请求进来会进入web容器中,从上到下按顺序执行过滤器。当执行到DelegatingFilterProxy过滤器中,会代理到FilterChainProxy类中。FilterChainProxy 决定应该使用哪个 SecurityFilterChain。只有第一个匹配的 SecurityFilterChain 被调用。

  1. 如果请求的URL是 /api/messages/,它首先与 /api/** 的 SecurityFilterChain0 模式匹配,所以只有 SecurityFilterChain0 被调用,尽管它也与 SecurityFilterChainn 匹配。
  2. 如果请求的URL是 /messages/,它与 /api/** 的 SecurityFilterChain0 模式不匹配,所以 FilterChainProxy 继续尝试每个 SecurityFilterChain。假设没有其他 SecurityFilterChain 实例相匹配,则调用 SecurityFilterChainn。

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

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

相关文章

C++学习day1

一>整理思维导图 二>提示并输入一个字符串 &#xff0c;统计活该字符串中的大写&#xff0c;小写&#xff0c;数字&#xff0c;空格&#xff0c;以及其他字符的个数要求使用c风格符完成 #include <iostream>int main() {std::string input;std::cout << &qu…

网络和系统操作命令

目录 ping&#xff1a;用于检测网络是否通畅&#xff0c;以及网络时延情况。ipconfig&#xff1a;查看计算机的IP参数配置信息&#xff0c;如IP地址、默认网关、子网掩码等信息。netstat&#xff1a;显示协议统计信息和当前TCP/IP网络连接。tasklist&#xff1a;显示当前运行的…

Go复合类型之数组类型

Go复合类型之数组 文章目录 Go复合类型之数组一、数组(Array)介绍1.1 基本介绍1.2 数组的特点 二、数组的声明与初始化2.1 数组声明2.2 常见的数据类型声明方法2.3 数组的初始化方式一&#xff1a;使用初始值列表初始化数组方法二&#xff1a;根据初始值个数自动推断数组长度方…

温故知新:dfs模板-843. n-皇后问题

n−n−皇后问题是指将 nn 个皇后放在 nnnn 的国际象棋棋盘上&#xff0c;使得皇后不能相互攻击到&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 nn&#xff0c;请你输出所有的满足条件的棋子摆法。 输入格式 共一行&#xff0c;包含整数 n…

通过位运算,实现单字段标识多个状态位

可能经常有如下这种需求: 需要一张表,来记录学员课程的通过与否. 课程数量不确定,往往很多,且会有变动,随时可能新增一门课. 这种情况下,在设计表结构时,一门课对应一个字段,就有些不合适, 因为不知道课程的具体数量,也无法应对后期课程的增加. 考虑只用一个状态标志位,利用位运…

精确到区县级街道乡镇行政边界geojson格式矢量数据的获取拼接实现Echarts数据可视化大屏地理坐标信息地图的解决方案

在Echarts制作地理信息坐标地图时&#xff0c;最麻烦的就是街道乡镇级别的行政geojson的获取&#xff0c; 文件大小 788M 文件格式 .json格式&#xff0c;由于是大文件数据&#xff0c;无法直接使用记事本或者IDE编辑器打开&#xff0c;推荐Dadroit Viewer&#xff08;国外…

【代码实践】HAT代码Window平台下运行实践记录

HAT是CVPR2023上的自然图像超分辨率重建论文《activating More Pixels in Image Super-Resolution Transformer》所提出的模型。本文旨在记录在Window系统下运行该官方代码&#xff08;https://github.com/XPixelGroup/HAT&#xff09;的过程&#xff0c;中间会遇到一些问题&am…

linux系统中常见注册函数的使用方法

大家好&#xff0c;今天给大家分享一下&#xff0c;linux系统中常见的注册函数register_chrdev_region()、register_chrdev()、 alloc_chrdev_region()的使用方法​。 一、函数包含的头文件&#xff1a; 分配设备编号&#xff0c;注册设备与注销设备的函数均在fs.h中申明&…

OMV6 安装Extras 插件失败的解决方法

# Time: 2023/10/07 #Author: Xiaohong # 运行环境: OS: OMV6 # 功能: 安装Extras 插件失败的解决方法 问题描述&#xff1a;OMV6 安装插件omv-extras&#xff0c;只能按如下提示的命令行&#xff0c;但安装过程中&#xff0c;会提示raw.githubusercontent.com 无法访问插…

校招秋招,性格和职业有关系吗?

企业在招聘应届毕业生时不再局限于普通的面试或者笔试&#xff0c;在互联网时代&#xff0c;为了能够更好的匹配需要的优质人才&#xff0c;企业会通过各种测试来提高招聘的准确率以及成功率。也许以前很多人都听说过性格和职业是有一定关系的&#xff0c;但是如何确定自己的性…

win安装ESPHome教程

感谢 Little-Bear-Bear 博主的教程,ESPHome入门 windows环境安装 -01 1.安装python 从Python官方网站下载。 确保选中“将Python添加到PATH”&#xff0c;然后一直通过 安装。 安装后重新启动计算机 winR 。按回车键。cmd python --version输入 查看 是否安装成功 python…

基于虚拟同步发电机控制的双机并联Simulink仿真模型

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

基于LADRC自抗扰控制的VSG三相逆变器预同步并网控制策略(Simulink仿真实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

8.2 JUC - 4.Semaphore

目录 一、是什么&#xff1f;二、简单使用三、semaphore应用四、Semaphore原理 一、是什么&#xff1f; Semaphore&#xff1a;信号量&#xff0c;用来限制能同时访问共享资源的线程上限 二、简单使用 public class TestSemaphore {public static void main(String[] args) …

freeRTOS的数据类型定义(portmacro.h文件里)

freeRTOS的数据类型定义&#xff08;portmacro.h文件里&#xff09; 解读&#xff1a; https://www.bilibili.com/video/BV1n8411y7DF/?buvidXY4F2994565924F2FFA7142AEEB2220A602A4&is_story_h5false&mid%2B8iBjJuKH0VIWaMd8h0wSQ%3D%3D&p1&plat_id114&s…

铭控传感亮相2023国际物联网展,聚焦“多场景物联感知方案”应用

金秋九月&#xff0c;聚焦IoT基石技术&#xff0c;荟萃最全物联感知企业&#xff0c;齐聚IOTE 2023第20届国际物联网展深圳站。铭控传感携智慧楼宇&#xff0c;数字工厂&#xff0c;智慧消防&#xff0c;智慧泵房等多场景物联感知方案及多品类无线传感器闪亮登场&#xff0c;现…

windows内核编程(2021年出版)笔记

1. Windows内部概览 1.1 进程 进程包含以下内容&#xff1a; 可执行程序&#xff0c;代码和数据私有的虚拟地址空间&#xff0c;分配内存时从这里分配主令牌&#xff0c;保存进程默认安全上下文&#xff0c;进程中的线程执行代码时会用到它私有句柄表&#xff0c;保存进程运…

C语言之共用体、枚举类型、typedef

共用体 共用体的所有成员共享同一个内存地址 插入一个知识点&#xff1a;字符串不可以直接赋值&#xff0c;要不就在定义的时候赋值&#xff0c;要不就只能使用scanf函数赋值或者<string>中的strcpy赋值 证明 如果要同时访问联合体中多个成员的值...... 则会出现以下情…

自动化项目实战->测试博客系统

1.熟悉项目-->哪些场景容易出现问题 2.针对核心流程设计测试用例(手工测试用例) 3.将手工测试用例转换为自动化测试用例 4.部署到服务器 一、针对核心流程设计测试用例 二、将手工测试用例转换为自动化测试用例 2.1设计自动化测试用例的代码结构 初始化动作:BeforeAll--…

[NISACTF 2022]popchains - 反序列化+伪协议

[NISACTF 2022]popchains 一、解题流程二、小小疑惑 一、解题流程 1、链条&#xff1a;Road_is_Long&#xff08;construct->wakeup【page$r】-> toString【string$m】&#xff09;-> Make_a_Change&#xff08;construct->get【effort$t】&#xff09;-> Try_W…