HttpSecurity 是如何组装过滤器链的

有小伙伴们问到这个问题,简单写篇文章和大伙聊一下。

一 SecurityFilterChain

首先大伙都知道,Spring Security 里边的一堆功能都是通过 Filter 来实现的,无论是认证、RememberMe Login、会话管理、CSRF 处理等等,各种功能都是通过 Filter 来实现的。

所以,我们配置 Spring Security,说白了其实就是配置这些 Filter。

以前旧版继承自 WebSecurityConfigurerAdapter 类,然后重写 configure 方法,利用 HttpSecurity 去配置过滤器链,这种写法其实不太好理解,特别对于新手来说,可能半天整不明白到底配置了啥。

现在新版写法我觉得更加合理,因为直接就是让开发者自己去配置过滤器链,类似下面这样:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return http.build();
}

这样开发者更容易理解,自己是在配置配置 SecurityFilter 过滤器链,因为就是要配置这样一个 Bean。

SecurityFilterChain 是一个接口,这个接口只有一个实现类 DefaultSecurityFilterChain。

DefaultSecurityFilterChain 中有一个 requestMatcher,通过 requestMatcher 可以识别出来哪些请求需要拦截,拦截下来之后,经由该类的另外一个属性 filters 进行处理,这个 filters 中保存了我们配置的所有 Filter。

public final class DefaultSecurityFilterChain implements SecurityFilterChain {private final RequestMatcher requestMatcher;private final List<Filter> filters;}

因此,在配置 SecurityFilterChain 这个 Bean 的时候,我们甚至可以按照如下方式来写:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return new DefaultSecurityFilterChain(new AntPathRequestMatcher("/**"));
}

这个配置就表示拦截所有请求,但是拦截下来之后,这些请求所经过的过滤器为空,即拦截所有请求但是不做任何处理。

我们也可以为 DefaultSecurityFilterChain 对象去配置过滤器链,大家看到,它的构造器可以传入 Filter:

但是,Spring Security 过滤器数量有 30+,我们平日开发使用的也在 15 个左右,这样一个一个去配置太麻烦了,每个过滤器并非 new 出来就能用了,还要配置各种属性,所以我们一般不会自己一个一个去配置这些过滤器。

二 SecurityConfigurer

为了简化 Spring Security 中各个组件的配置,官方推出了 SecurityConfigurer,SecurityConfigurer 在 Spring Security 中扮演着非常重要的角色,其主要作用可以归纳为以下几点:

  1. 配置过滤器:在 Spring Security 中,过滤器链中的每一个过滤器都是通过 xxxConfigurer 来进行配置的,而这些 xxxConfigurer 实际上都是 SecurityConfigurer 的实现。这意味着 SecurityConfigurer 负责配置和管理安全过滤器链中的各个组件。
  2. 初始化和配置安全构建器:SecurityConfigurer 接口中定义了两个主要方法:init 和 configure。init 方法用于初始化安全构建器(SecurityBuilder),而 configure 方法则用于配置这个构建器。这两个方法共同确保了安全组件的正确设置和初始化。
  3. 提供扩展点:SecurityConfigurer 的三个主要实现类(SecurityConfigurerAdapter、GlobalAuthenticationConfigurerAdapter、WebSecurityConfigurer)为开发者提供了扩展 Spring Security 功能的点。特别是,大部分的 xxxConfigurer 都是 SecurityConfigurerAdapter 的子类,这使得开发者能够轻松地定制和扩展安全配置。
  4. 构建安全上下文:通过配置和组合不同的 SecurityConfigurer 实现,可以构建一个完整的安全上下文,包括身份验证、授权、加密等各个方面,从而确保应用程序的安全性。

换句话说,Spring Security 中的各种 Filter,其实都有各自对应的 xxxConfigurer,通过这些 xxxConfigurer 完成了对 Filter 的配置。

举几个简答的例子,如:

  • CorsConfigurer 负责配置 CorsFilter
  • RememberMeConfigurer 负责配置 RememberMeAuthenticationFilter
  • FormLoginConfigurer 负责配置 UsernamePasswordAuthenticationFilter

以上是前置知识。

三 SecurityBuilder

SecurityBuilder 是 Spring Security 框架中的一个核心接口,它体现了建造者设计模式,用于构建和配置安全组件。这个接口并不直接与安全功能如认证或授权的具体实现绑定,而是提供了一种灵活的方式来组织和装配这些功能组件,特别是在构建安全上下文和过滤器链过程中。

核心特点

  1. 灵活性与模块化:通过 SecurityBuilder,开发者可以以模块化的方式添加、移除或替换安全配置中的各个部分,而不需要了解整个安全架构的细节。这促进了代码的复用性和可维护性。
  2. 层次结构:在 Spring Security 中,SecurityBuilder及其子类形成了一个层次结构,允许配置像嵌套娃娃一样层层深入,每一个层级都可以贡献自己的配置逻辑,最终形成一个完整的安全配置。
  3. 安全上下文构建:在认证过程中,SecurityBuilder 用于构造 SecurityContext,该上下文中包含了当前认证主体(通常是用户)的详细信息,以及主体所关联的权限和角色。
  4. 过滤器链构建:在 Web 应用中,SecurityBuilder 还用于构建 FilterChainProxy,这是一个关键组件,负责组织和执行一系列的过滤器,这些过滤器负责处理 HTTP 请求的安全性,比如检查用户是否已经登录、是否有权限访问某个资源等。

简而言之,在 SecurityBuilder 的子类 AbstractConfiguredSecurityBuilder 中,有一个名为 configurers 的集合,这个集合中保存的就是我们前面所说的各种 xxxConfigure 对象。

AbstractConfiguredSecurityBuilder 收集到所有的 xxxConfigure 之后,将来会调用每个 xxxConfigure 的 configure 方法完成过滤器的构建。

另外很重要的一点,HttpSecurity 也是一个 SecurityBuilder。因此,HttpSecurity 其实就是帮我们收集各种各样的 xxxConfigure,并存入到 configurers 集合中,以备将来构建过滤器使用。

四 HttpSecurity

根据前面的介绍,我们知道,无论我们怎么配置,最终拿到手的一定是一个 DefaultSecurityFilterChain 对象,因为这是 SecurityFilterChain 的唯一实现类。

所以,HttpSecurity 其实就是在帮我们配置 DefaultSecurityFilterChain,我们看到 HttpSecurity 里边就有两个非常关键的属性:

private List<OrderedFilter> filters = new ArrayList<>();
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;

这两个恰恰就是构建 DefaultSecurityFilterChain 所需的关键参数。

事实确实如此,我们看到,在 HttpSecurity#performBuild 方法中,就是利用这两个参数构建出来了 DefaultSecurityFilterChain:

@Override
protected DefaultSecurityFilterChain performBuild() {ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(ExpressionUrlAuthorizationConfigurer.class);AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;this.filters.sort(OrderComparator.INSTANCE);List<Filter> sortedFilters = new ArrayList<>(this.filters.size());for (Filter filter : this.filters) {sortedFilters.add(((OrderedFilter) filter).filter);}return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}

这里先对 filters 进行排序,然后据此创建出来 DefaultSecurityFilterChain 对象。

那么问题来了,filters 中的过滤器从何而来呢?

前面我们提到,HttpSecurity 本质上也是一个 SecurityBuilder,我们平时在 HttpSecurity 配置的各种东西,本质上其实就是一个 xxxConfigure,这些 xxxConfigure 被 HttpSecurity 收集起来,最后会遍历收集起来的 xxxConfigure,调用其 configure 方法,最终获取过滤器,并将获取到的过滤器存入到 filters 集合中。

这里松哥以我们最为常见的登录配置为例来和大家捋一捋这个流程。

新版的登录配置我们一般按照如下方式来配置:

http.formLogin(f -> f.permitAll());

这个方法如下:

public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));return HttpSecurity.this;
}

从这段源码可以看到,我们配置里边写的 lambda,其实就是在配置 FormLoginConfigurer 对象。

getOrApply 方法主要主要有两方面的作用:

  1. 确保这个 Bean 不会重复配置。
  2. 如果该 Bean 是第一次配置,那么将该对象添加到 SecurityBuilder 中(其实就是 HttpSecurity 对象自身),这个 SecurityBuilder 里边保存了所有配置好的 xxxConfigure 对象,将来在过滤器链构建的时候,会去遍历这些 xxxConfigure 对象并调用其 configure 方法,完成过滤器的构建。

在 FormLoginConfigurer#init 方法中,完成了和登录相关过滤器的配置,如:

  • 登录请求处理过滤器(构造方法中完成的)
  • 注销过滤器的配置
  • 登录失败端点配置
  • 登录页面的配置

这里涉及到的属性都会在对应的 xxxConfigurer 中完成配置。

当过滤器链开始构建的时候,会调用到所有 xxxConfigurer 的 configure 方法,在这个方法中,最终完成相关过滤器的创建,并将之添加到 HttpSecurity 的 filters 属性这个集合中。

FormLoginConfigurer 的 configure 方法在其父类中,如下:

@Override
public void configure(B http) throws Exception {PortMapper portMapper = http.getSharedObject(PortMapper.class);if (portMapper != null) {this.authenticationEntryPoint.setPortMapper(portMapper);}RequestCache requestCache = http.getSharedObject(RequestCache.class);if (requestCache != null) {this.defaultSuccessHandler.setRequestCache(requestCache);}this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));this.authFilter.setAuthenticationSuccessHandler(this.successHandler);this.authFilter.setAuthenticationFailureHandler(this.failureHandler);if (this.authenticationDetailsSource != null) {this.authFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);}SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);if (sessionAuthenticationStrategy != null) {this.authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);}RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);if (rememberMeServices != null) {this.authFilter.setRememberMeServices(rememberMeServices);}SecurityContextConfigurer securityContextConfigurer = http.getConfigurer(SecurityContextConfigurer.class);if (securityContextConfigurer != null && securityContextConfigurer.isRequireExplicitSave()) {SecurityContextRepository securityContextRepository = securityContextConfigurer.getSecurityContextRepository();this.authFilter.setSecurityContextRepository(securityContextRepository);}this.authFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());F filter = postProcess(this.authFilter);http.addFilter(filter);
}

关键在最后这一句 http.addFilter(filter);,这句代码将配置好的过滤器放到了 HttpSecurity 的 filters 集合中。

其他的也都类似,例如配置 CorsFilter 的 CorsConfigurer#configure 方法,如下:

@Override
public void configure(H http) {ApplicationContext context = http.getSharedObject(ApplicationContext.class);CorsFilter corsFilter = getCorsFilter(context);http.addFilter(corsFilter);
}

这些被添加到 HttpSecurity 的 filters 属性上的 Filter,最终就成为了创建 DefaultSecurityFilterChain 的原材料。

类似的道理,我们也可以分析出 disable 方法的原理,例如我们要关闭 csrf,一般配置如下:

.csrf(c -> c.disable());

那么很明显,这段代码其实就是调用了 CsrfConfigurer 的 disable 方法:

public B disable() {getBuilder().removeConfigurer(getClass());return getBuilder();
}

该方法直接从 SecurityBuilder 的 configurers 集合中移除了 CsrfConfigurer,所以导致最终调用各个 xxxConfigure 的 configure 方法的时候,没有 CsrfConfigurer#configure 了,就导致 csrf 过滤器没有配置上,进而 CSRF filter 失效。

如果小伙伴们想要彻底学会 Spring Security,那么不妨看看我最近刚刚录完的 Spring Security+OAuth2 教程。

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

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

相关文章

virtual cache基本概念

Virtual cache的引入 TLB只是加速了从虚拟地址到物理地址的转换,可以很快地得到所需要的数据(或指令)在物理内存中的位置,也就是得到了物理地址但是,如果直接从物理内存中取数据(或置指令),显然也是很慢的,因此可以使用在以前章节提到的Cache来缓存物理地址到数据的转换过程。实…

集成电路的引脚分布规律及识别

集成电路的引脚分布规律根据不同的封装方式而确定&#xff0c;引脚的序号和集成电路图中的编号是一一对应的&#xff0c;识别集成电路的引脚号对分析集成电路的内部框图和工作原理&#xff0c;以及排除集成电路的故障都具有重要的意义。 1.单列集成电路引脚的分布规律及识别 …

区块链技术:金融行业的信任基石

一、引言 在数字化转型的浪潮中,区块链技术以其独特的优势,正逐渐成为金融行业的信任基石。本文将深入探讨区块链技术在金融行业的应用,通过具体案例的细化和完善,分析其特点和影响。 二、区块链技术的核心特点 1. 去中心化:区块链技术通过分布式账本,实现去中心化的数…

栈和队列题目练习

本节小编选了两道题来加深对栈和队列的认识理解&#xff01; 有效的括号 方法1&#xff1a;直接用栈的结构&#xff08;动态数组&#xff09; 本题可以用栈这个结构来解答&#xff0c;将(,{,[ 左括号压入栈中&#xff0c;然后取出栈顶元素与右括号),},]匹配。不匹配的话&…

【Qt】【模型视图架构】 在项目视图中启用拖放

文章目录 1. 在便捷类中启用拖放2. 在模型/视图类中启用拖放 模型/视图框架支持Qt的拖放应用。 列表、表格和树中的项目可以在视图中被拖拽&#xff0c;数据作为MIME编码的数据被导入和导出。标准视图可以自动支持内部的拖放。 默认视图的拖放功能并没有被启用&#xff0c;如果…

opencv进阶 ——(八)图像处理之RMBG模型AI抠图

BRIA.AI团队于HuggingFace开源了一个基于ISNet背景移除模型RMBG-1.4&#xff0c;它可以有效对前景与背景进行分离。RMBG-1.4在精心构建的数据集上训练而来&#xff0c;该数据包含常规图像、电商、游戏以及广告内容&#xff0c;该方案达到了商业级性能&#xff0c;但仅限于非商业…

【PHP项目实战训练】——laravel框架的实战项目中可以做模板的增删查改功能(1)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

【管理咨询宝藏120】顶级咨询公司领导管控优化设计方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏120】顶级咨询公司领导管控优化设计方案 【格式】PDF版本 【关键词】人力资源、组织管控、领导力 【核心观点】 - 管的过细” “高层的管理要往…

【排序】冒泡排序

在我们的生活中&#xff0c;到处都离不开排序的作用&#xff0c;考试分数要排序&#xff0c;商场购物要排序&#xff0c;可以说排序对我们来说处处存在&#xff0c;那么从本章开始&#xff0c;我将要依次分享一些排序方法&#xff0c;从易到难&#xff0c;包括冒泡&#xff0c;…

随身wifi和手机流量卡,你知道该怎么选吗?

网络已经成为我们这个时代的代名词&#xff01; 那么&#xff0c;在上网的时代&#xff0c;我们有很多问题都要考虑&#xff0c;比如如何选择上网方式&#xff0c;是选择一张流量卡&#xff0c;还是一个随身WIFI&#xff1f; 听小编一句劝&#xff0c;先不要着急买&#xff0c…

接口测试工具:Postman的下载安装及使用

1 Postman 介绍 1.1 Postman 是什么 Postman 是一款功能超级强大的用于发送 HTTP 请求的 测试工具 做 WEB 页面开发和测试的人员常用工具 创建和发送任何的 HTTP 请求(Get/Post/Put/Delete...) 1.2 Postman 相关资源 1.2.1 官方网站&#xff1a;https://www.postman.com/ …

sde the Upgrade Geodatabase tool to install/upgrade system tables (-64).

数据库端迁移sde导入后&#xff0c;在sde端启动服务报错&#xff1a; ------------------------------------------------------- ArcSDE 10.0 for Oracle11g Build 685 Fri May 14 12:05:43 2010 ------------------------------------------------------- ST_Geometry Sc…

Java——面向对象初阶

前言&#xff1a; Java面向对象相关讲解 文章目录 一、面向对象二、类与对象三、封装四、构造方法及重载五、this关键字六、基本数据类型和引用数据类型七、JavaBean类 一、面向对象 面向对象&#xff08;Object-Oriented Programming, OOP&#xff09;是一种编程范式&#xff…

C++第二十二弹---vector深度剖析及模拟实现(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、容量操作 2、内容修改操作 3、打印函数 4、迭代器失效 4.1、什么是迭代器失效 4.2、哪些操作会引起迭代器失效 总结 1、容量操作 size()…

Idea的相关操作

1、关闭自动更新 点击左上角File->Setting&#xff0c;进入配置页面&#xff0c;点击Appearance & Behavior > System Settings > Updates&#xff0c;取消勾选更新选项&#xff0c;如图&#xff1b; 2、代码提示忽略大小写 点击左上角File->Setting&#xf…

用Unityhub安装unity2018.3.0和vuforia

打开下载网址 https://unity.cn/releases/full/2018 选择2018.3.x 找到2018.3.0后&#xff0c;点击从UnityHub下载 然后unityhub会弹出安装界面 只勾选这两个&#xff0c;其余的全部取消勾选&#xff0c;默认勾选上的也取消掉&#xff0c;然后点击安装

【数据分享】2009-2022年我国省份级别的轨道交通相关指标(20多项指标)

《中国城市建设统计年鉴》中细致地统计了我国城市市政公用设施建设与发展情况&#xff0c;在之前的文章中&#xff0c;我们分享过基于2006-2022年《中国城市建设统计年鉴》整理的2006—2022年我国省份级别的市政设施水平相关指标、2006-2022年我国省份级别的各类建设用地面积数…

String 类

目录&#xff1a; 一. 认识 String 类 二. String 类的基本用法 三. String对象的比较 四.字符串的不可变性 五. 认识 StringBuffer 和 StringBuilder 一. 认识 String 类&#xff1a; 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者…

Unity2D横版摄像机跟随

在Unity2D横版游戏中&#xff0c;摄像机跟随是一个非常重要的功能。一个流畅的摄像机跟随系统可以让玩家更好地沉浸在游戏世界中。本文将介绍如何在Unity中实现2D横版摄像机跟随&#xff0c;并分享一些优化技巧。 一、准备工作 在开始实现摄像机跟随之前&#xff0c;请确保您…

MFC 模态对话框的实现原理

参考自MFC 模态对话框的实现原理 - 西昆仑 - OSCHINA - 中文开源技术交流社区 1. 模态对话框 在涉及 GUI 程序开发的过程中&#xff0c;常常有模态对话框以及非模态对话框的概念 模态对话框&#xff1a;在模态对话框活动期间&#xff0c;父窗口是无法进行消息响应&#xff0…