【SpringSecurity源码】过滤器链加载流程


theme: smartblue
highlight: a11y-dark

一、前言及准备

1.1 SpringSecurity过滤器链简单介绍

在Spring Security中,过滤器链(Filter Chain)是由多个过滤器(Filter)组成的,这些过滤器按照一定的顺序对进入应用的请求进行处理。每个过滤器可以执行不同的安全操作,如身份验证、授权、安全上下文的建立等。

过滤器是一种典型的AOP思想,我们将通过源码分析这些过滤器如何被加载以及组成过滤器链。

1.2 SpringSecurity配置类

该类主要对SpringSecurity进行一系列配置,后续过滤器链的初始化和加载也是基于这个配置类。当前配置类仅供参考。注意里面两个很重要的方法 #configure(WebSecurity web)以及#configure(WebSecurity web),他们对过滤器链的初始化很重要。

@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {// 自定义登录成功或失败处理器,退出登录处理器@Autowiredprivate MyAuthenticationService myAuthenticationService;// 自定义验证码过滤器@Autowiredprivate ValidateCodeFilter validateCodeFilter;// 自定义权限不足处理@Autowiredprivate MyAccessDeniedHandler myAccessDeniedHandler;// 权限相关Service@Autowiredprivate PermissionService permissionService;@Overridepublic void configure(WebSecurity web) throws Exception {//解决静态资源被拦截的问题web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/code/**");}/*** http请求方法** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {// 加入用户名密码验证过滤器的前面http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);// 查询数据库所有权限列表List<Permission> list = permissionService.list();for (Permission permission : list) {// 添加请求权限http.authorizeRequests().antMatchers(permission.getPermissionUrl()).hasAuthority(permission.getPermissionTag());}// 设置权限不足的信息http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);http.formLogin()// 开启表单认证.loginPage("/toLoginPage")// 自定义登录页面.loginProcessingUrl("/login")//表单提交的路径.usernameParameter("username").passwordParameter("password")//自定义input的name值.successForwardUrl("/")//登录成功之后跳转的路径.successHandler(myAuthenticationService).failureHandler(myAuthenticationService)//登录成功或者失败的处理.and().logout().logoutUrl("/logout").logoutSuccessHandler(myAuthenticationService).and().rememberMe()//开启记住我功能.tokenValiditySeconds(1209600)//token失效时间 默认是2周.rememberMeParameter("remember-me")//自定义表单input值.tokenRepository(getPersistentTokenRepository()).and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面.anyRequest().authenticated();//关闭csrf防护http.csrf().disable();//加载同源域名下iframe页面http.headers().frameOptions().sameOrigin();// 开启跨域支持http.cors().configurationSource(corsConfigurationSource());}} 

1.3 过滤器链加载方法入口

Spring boot启动中会加载spring.factories文件, 在文件中有对应针对Spring Security的过滤器链
的配置信息,所以spring.factories来找过滤器链加载方法入口

image.png

所以我们的过滤器链加载方法入口位于org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain()

二、 源码导读

SpringSecurity的过滤器链创建本质就是读取SecurityConfig配置类,并且通过核心配置方法:#configure(WebSecurity web)以及#configure(WebSecurity web)生成一个个配置对象,最终每个配置对象会转换成一个过滤器对象,这些过滤器对象组成过滤器链。

前面已经提到了,配置类中的 #configure(WebSecurity web)以及#configure(WebSecurity web)非常重要。他们将分别加载到一个HttpSecurity对象和一个名为ignoredRequests的ArrayList之中,
并且过滤器链也是由这两部分转换后组成

2.1 SpringSecurity配置信息的加载

由前面的内容我们已经定位到过滤器链加载的方法入口位于:
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain()

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {boolean hasConfigurers = webSecurityConfigurers != null&& !webSecurityConfigurers.isEmpty();if (!hasConfigurers) {WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {});webSecurity.apply(adapter);}// 加载方法return webSecurity.build();
}

我们从该方法的webSecurity.build()按照下图一路点击,最终会进入到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()
方法,该方法是加载配置和过滤器链的主要方法

image.png

org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()如下

@Override
protected final O doBuild() throws Exception {synchronized (configurers) {buildState = BuildState.INITIALIZING;beforeInit();// 【1】.初始化,主要构建HttpSecurity对象以及内部的配置对象init();buildState = BuildState.CONFIGURING;beforeConfigure();// 【2】ignoredRequests集合的构建configure();buildState = BuildState.BUILDING;// 【3】组装过滤器链O result = performBuild();buildState = BuildState.BUILT;return result;}

可以看到,当前的configurers加载的就是我们自定义的配置类

image.png

2.1.1 构建带有配置信息的HttpSecurity对象

我们从上述的doBuild()方法体中点击init()方法进入其内部

org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#init()

private void init() throws Exception {Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();for (SecurityConfigurer<O, B> configurer : configurers) {// 构建HttpSecurity对象configurer.init((B) this);}for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {configurer.init((B) this);}
}

接着点击构建HttpSecurity的对象的方法configurer.init()方法

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#init(),发现真正构建HttpSecurity的方法其实是 getHttp()

public void init(final WebSecurity web) throws Exception {// 真正构建HttpSecurity的方法final HttpSecurity http = getHttp();// 将HttpSecurity对象作为SecurityFilterChainBuilder对象返回web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);web.securityInterceptor(securityInterceptor);});
}

点击进入 getHttp()

protected final HttpSecurity getHttp() throws Exception {/**省略其他**/// 【1】.新建一个HttpSecurity对象http = new HttpSecurity(objectPostProcessor, authenticationBuilder,sharedObjects);if (!disableDefaults) {// 【2】.设置HttpSecurity对象中configurers配置对象以及filter过滤器对象http.csrf().and() .addFilter(new WebAsyncManagerIntegrationFilter()) .exceptionHandling().and().headers().and().sessionManagement().and().securityContext().and().requestCache().and().anonymous().and().servletApi().and().apply(new DefaultLoginPageConfigurer<>()).and().logout();/**省略**/}// 【3】.执行SecurityConfig中的configure(HttpSecurity http)方法configure(http);return http;
}

观察上述代码我们发现,首先通过new HttpSecurity()新建了一个HttpSecurity对象,并且在下方设置HttpSecurity对象中configurers属性以及filter属性。

当执行完下面代码时,即【2】处的代码

        http.csrf().and() //【添加CsrfConfigurer】.addFilter(new WebAsyncManagerIntegrationFilter())  // 【添加 WebAsyncManagerIntegrationFilter】.exceptionHandling().and() // 【添加 ExceptionHandlingConfigurer】.headers().and() // 【添加 HeadersConfigurer】.sessionManagement().and()// 【添加SessionManagementConfigurer】.securityContext().and()// 【添加SecurityContextConfigurer】.requestCache().and()// 【添加RequestCacheConfigurer】.anonymous().and()// 【添加AnonymousConfigurer】.servletApi().and()// 【添加ServletApiConfigurer】.apply(new DefaultLoginPageConfigurer<>()).and()//【添加DefaultLoginPageConfigurer】.logout(); // 【添加LogoutConfigure】r

HttpSecurity对象中configurers属性以及filter如下,此时size分别为 101

image.png
image.png

执行了【2】的代码后会执行【3】的代码:
configure(http);
该方法会调用SecurityConfig配置类文件的configure()方法,由于此时传入的HttpSecurity对象,所以调用的
SecurityConfig配置类文件中的configure(HttpSecurity http),代码如下(注意该部分为自定义的配置文件中方法,需要伙伴自己去编写,根据自己需求进行配置。当前仅供参考!):

@Override
protected void configure(HttpSecurity http) throws Exception {// 加入用户名密码验证过滤器的前  【添加ValidateCodeFilter】http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);// 查询数据库所有权限列表List<Permission> list = permissionService.list();【添加ExpressionUrlAuthorizationConfigurer】for (Permission permission : list) {// 添加请求权限http.authorizeRequests().antMatchers(permission.getPermissionUrl()).hasAuthority(permission.getPermissionTag());}// 设置权限不足的信息 http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);http.formLogin()// 开启表单认证 【添加FormLoginConfigurer】.loginPage("/toLoginPage")// 自定义登录页面.loginProcessingUrl("/login")//表单提交的路径.usernameParameter("username").passwordParameter("password")//自定义input的name值.successForwardUrl("/")//登录成功之后跳转的路径.successHandler(myAuthenticationService).failureHandler(myAuthenticationService)//登录成功或者失败的处理.and().logout().logoutUrl("/logout").logoutSuccessHandler(myAuthenticationService).and().rememberMe()//开启记住我功能 【添加 RememberMeConfigurer】.tokenValiditySeconds(1209600)//token失效时间 默认是2周.rememberMeParameter("remember-me")//自定义表单input值.tokenRepository(getPersistentTokenRepository()).and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面.anyRequest().authenticated();//关闭csrf防护// 【移除CsrfConfigurer】http.csrf().disable();//加载同源域名下iframe页面http.headers().frameOptions().sameOrigin();// 开启跨域支持// 【添加CorsConfigurer】http.cors().configurationSource(corsConfigurationSource());}

执行完该方法后,configurers属性以及filter属性如下,size变为132

image.png
image.png

HttpSecurity对象构建完成!,之后configurers中每个Configurer对象最终都会转换为Filter对象

2.1.2 列表集合 ignoredRequests 的创建

当执行完init()后,HttpSecurity对象构建完成,并且存放在一个叫做securityFilterChainBuilders的集合对象中。

image.png

此时我们回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild(),在执行init()方法之后会有一个configure()方法,点击进去可以看到:

private void configure() throws Exception {Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();for (SecurityConfigurer<O, B> configurer : configurers) {// 【加载配置类SecurityConfig中的configure(WebSecurity web)方法】configurer.configure((B) this);}
}

注意看

configurer.configure((B) this);

这句代码,此时的this是一个WebSecurity对象
image.png

仔细想想,在配置类文件中,不正是有一个#configure(WebSecurity web)配置方法吗?没错,这句代码正是执行了配置文件中的该方法。

#configure(WebSecurity web)方法体:

image.png

执行完这一行代码之后,ignoredRequests变量已经被赋值,而且变量中的值是和上面配置方法中配置对应的。

image.png

OK,此时SpringSecurity配置信息的加载就已经完成,接下来就是将这些配置信息转换成过滤器并组成过滤器链

2.2 过滤器链的加载

加载完配置文件后,我们的配置信息分别被存放在两部分ignoredRequestssecurityFilterBulders之中,下面再来看看这两部分是如何构建成过滤器链的。

2.2.1 ignoredRequests 转换成过滤器链

image.png
此时我们再次回到此时我们回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()
找到构造过滤器链的方法performBuild()

org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild()

@Override
protected Filter performBuild() throws Exception {/**忽略部分代码**/【1】计算过滤器链长度int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);【2】将ignoredRequests集合中的对象转为过滤器加入到过滤器链中for (RequestMatcher ignoredRequest : ignoredRequests) {securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));}【3】将securityFilterChainBuilder转为过滤器加入到过滤器链中for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {securityFilterChains.add(securityFilterChainBuilder.build());}/**忽略部分代码**/}/**忽略部分代码**/return result;
}

【1】处代码计算过滤器链长度chainSize,Debug中计算结果如下

image.png

【2】处代码将ignoredRequests集合中的对象转为过滤器加入到过滤器链securityFilterChains
中,执行结果如下

image.png

ignoredRequests转换成过滤器链完成

【3】处的代码就是 securityFilterBulders的的处理。下面接着讲。

2.2.2 securityFilterBulders 转换成过滤器链

在上面的org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild() 方法中通过观察看到,处理securityFilterBulders代码就一句话

securityFilterChains.add(securityFilterChainBuilder.build());

我们从securityFilterChainBuilder.build(),按照下面顺序一路点击:

image.png

我们将会定位到
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#configure()

private void configure() throws Exception {【1】获取配置对象Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();for (SecurityConfigurer<O, B> configurer : configurers) {【2】将配置对象加入到过滤器链configurer.configure((B) this);}
}

在【1】处,我们将得到从securityFilterChainBuilder中得到configurers配置列表

image.png

在【2】处,会循环配置列表对每个配置对象处理并且最终加入到过滤器链中。
比如当前是ExceptionHandlingConfigurer
那么就会执行
org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer#configure()

@Override
public void configure(H http) {AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);// 【1】创建 ExceptionTranslationFilterExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http));AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);exceptionTranslationFilter = postProcess(exceptionTranslationFilter);//【2】 ExceptionTranslationFilter加入到过滤器链http.addFilter(exceptionTranslationFilter);
}

再比如当前是HeaderConfigurer,那么会执行org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#configure()

@Override
public void configure(H http) {【1】创建 HeaderWriterFilterHeaderWriterFilter headersFilter = createHeaderWriterFilter();【2】加入到过滤器链http.addFilter(headersFilter);
}

也就是说,具体是什么类型的Filter,是根据当前Configurer而决定的

当我们执行完所有的configure()方法,再次回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()

可以看到filter已经被加载出来

image.png

但是,过滤器是有一定顺序的,此时加载出来的过滤器并没有处理顺序。
当方法执行到 performBuild()内部时,会进行排序
org.springframework.security.config.annotation.web.builders.HttpSecurity#performBuild()

@Override
protected DefaultSecurityFilterChain performBuild() {【对过滤器进行排序】filters.sort(comparator);return new DefaultSecurityFilterChain(requestMatcher, filters);
}

排序后的过滤器链:

image.png

到这里,拦截器链加载结束!

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

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

相关文章

AI绘画Stable Diffusion 插件篇:智能标签提示词插件sd-danbooru-tags-upsampler

大家好&#xff0c;我是向阳。 关于智能标签提示词插件&#xff0c;在很早之前就介绍过很多款了&#xff0c;今天再给大家介绍一款智能标签提示词插件sd-danbooru-tags-upsampler。该智能提示词插件是今年2月23号才发布的第一版V0.1.0&#xff0c;算是比较新的智能提示词插件。…

简化多容器应用部署:深入理解 Docker Compose

Docker Compose 已成为现代应用开发和部署的核心工具之一。它为开发者提供了一种简单且高效的方式来定义、运行和管理多容器应用程序。无论是在本地开发环境还是生产环境中&#xff0c;Docker Compose 都能够帮助开发团队快速搭建、部署和扩展复杂的应用系统。本文将深入探讨 D…

第100+7步 ChatGPT文献复现:ARIMA-GRNN预测出血热

基于WIN10的64位系统演示 一、写在前面 这一次&#xff0c;我们来解读ARIMA-GRNN组合模型文章&#xff0c;也是老文章了&#xff1a; 《PLoS One》杂志的2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal…

代码随想录 动态规划 刷题记录

动态规划 动规的五部曲: 确定dp数组(dp table)以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组509.斐波那契数列 70.爬楼梯 746.使用最小花费爬楼梯 62.不同路径 63.不同路径II 343.整数拆分 96.不同的二叉搜索树 本轮树可以分为当1,2,3……

uni-app(五):原生插件打包并使用(Android)

原生插件打包并使用 解决Gradle不显示命令问题解决方法 运行打包查看打包好的包引入到uni-app项目中编写配置文件TestModuleTestComponent 制作基座并运行 解决Gradle不显示命令问题 解决方法 运行打包 查看打包好的包 引入到uni-app项目中 编写配置文件 TestModule {"n…

vue2 Avoided redundant navigation to current location

再次点击同一个链接会报错 每次使用 push 方法时带上两个回调函数 this.$router.push({name: item.name}, ()>{}, ()>{}) //第二、第三个参数分别为成功和失败的回调函数重写 Vue-router 原型对象上的 push 函数不行 https://blog.csdn.net/weixin_43615570/article/d…

Linux -- Git

Git Git 是一个开源的分布式版本控制系统&#xff0c;主要用于软件开发中的源代码管理。它由 Linus Torvalds 创建&#xff0c;也是 Linux 内核的开发者。Git 使得多个开发者可以在同一个项目上协作&#xff0c;而不会相互干扰。它允许开发者在本地计算机上创建多个分支&#…

番外篇 | YOLOv8改进之利用SCINet解决黑夜目标检测问题 | 低照度图像增强网络

前言:Hello大家好,我是小哥谈。自校正照明网络(Self-Calibrating Illumination Network, SCINet)是一种基于深度学习的图像照明算法,可以自动分析图像的内容并根据图像内容自动优化照明。SCINet是一种专为低光照图像增强设计的框架。它通过级联照明学习过程和权重共享机制…

Php php7的特性

1. 性能优化 PHP7引入了Zend Engine 3.0&#xff0c;显著提高了执行效率&#xff0c;相比PHP 5.x&#xff0c;性能提升了2-3倍。这个特性无法直接通过代码示例展示&#xff0c;但你可以感受到在升级到PHP7后&#xff0c;相同代码的执行速度更快。 2. 函数返回类型声明 允许在…

【系统分析师】软件架构设计

文章目录 1、构件与软件复用1.1 主流构件标准1.2 构件获取与管理1.3 构件复用的方法 2、软件架构概述3、软件架构建模4、软件架构风格4.1 经典架构风格4.2 层次架构风格4.3 富互联网应用-RIA 5、面向服务的架构5.1 SOA概述5.2 SOA的关键技术5.3 SOA的实现方法 6、软件架构评估6…

Nginx - server、listen、server_name和多服务请求处理(三)

server 上下文 server { }server指令用于配置一个虚拟服务。listen指令描述了本服务可接受链接的所有地址和端口&#xff0c;server_name指令列举了所有服务的名字&#xff0c;例如 server {listen 80;server_name example.org www.example.org; }listen 指令 Syntax: …

Rust - 基础语法

文章目录 注释基本输出输出 {} 变量重影&#xff08;Shadowing&#xff09; 数据类型整数型&#xff08;Integer&#xff09;浮点数型&#xff08;Floating-Point&#xff09; f32、f64数学计算布尔型 bool字符型 char元组 ()数组 [].. 表示范围切片 slice结构体枚举match 处理…

AI大模型探索之路-训练篇20:大语言模型预训练-常见微调技术对比

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

nginx 详解

Nginx&#xff08;发音为“Engine-X”&#xff09;是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3代理服务器。Nginx以其高稳定性、丰富的功能集、简单的配置和低资源消耗而闻名。它特别适用于处理高并发请求&#xff0c;这部分归功于其事件驱动和异步架构。…

RF Plasma gernerator-系列(RF-5KW Adtec)说明书TX06-9001-00

RF Plasma gernerator-系列(RF-5KW Adtec)说明书TX06-9001-00

深度学习课程论文精读——ESRGAN

目录 1.研究概述 2.论文创新 2.1 改进生成器的网络框架 2.2 改进判别器 2.3 改进感知损失 2.4 网络插值 3.实验 3.1 评价指标 3.2 训练细节 3.3 对比实验 3.4 消融实验 3.5 网络插值 4.总结 5.阅读参考 文章标题&#xff1a;《ESRGAN: Enhanced Super-Resolution…

Hive优化(1)——分桶采样

分桶&#xff08;Bucketing&#xff09; 分桶是将数据按照某个字段的哈希值进行分组存储的一种技术。它的原理是将数据按照指定字段的哈希值分成固定数量的桶&#xff0c;将每条记录分配到对应的桶中。分桶可以帮助优化特定类型的查询&#xff0c;例如连接查询和聚合操作&…

2024OD机试卷-攀登者1 (java\python\c++)

题目:攀登者1 题目描述 攀登者喜欢寻找各种地图,并且尝试攀登到最高的山峰。 地图表示为一维数组,数组的索引代表水平位置,数组的元素代表相对海拔高度。其中数组元素0代表地面。 例如:[0,1,2,4,3,1,0,0,1,2,3,1,2,1,0],代表如下图所示的地图,地图中有两个山脉位置分别…

TS-声明文件

目录 1&#xff0c;什么是声明文件2&#xff0c;作用3&#xff0c;位置1&#xff0c;tsconfig.json 配置的包含目录中2&#xff0c;node_modules/types 目录中3&#xff0c;typeRoots 配置项中的目录4&#xff0c;与 js 文件同名同目录的文件 4&#xff0c;编写1&#xff0c;ts…

java中List的toArray()方法

toArray()介绍 toArray()方法是List接口中提供的方法&#xff0c;用来实现List对象转换为数组对象的功能。 toArray()方法有两种形式&#xff0c;无参方法和带泛型的方法&#xff0c;接下来给出例子。 1.toArray() // toArray()源码public Object[] toArray() {return Arrays.…