Spring Security 源码解读 :基本架构及初始化

article/2024/5/23 2:20:58

Spring Security 是基于web的安全组件,所以一些相关类会分散在 spring-security包和web包中。Spring Security通过自定义Servlet的Filter的方式实现,具体架构可参考官网Spring Security: Architecture

这里使用Spring Boot 2.7.4版本,对应Spring Security 5.7.3版本

基本架构

在这里插入图片描述
首先左侧是Servlet中的Filter组成的FilterChain,Spring Security通过注册一个DelegatingFilterProxy的Filter,然后在该Proxy中内置多条Spring Security组织的Security Filter Chain(chain中套娃一个chain),一个Security Filter Chain又有多个Filter,通过不同的规则将Request匹配到第一个满足条件的Security Filter Chain。

Web源码

既然Spring Security涉及到Filter,而Filter是Servlet中的组件,这里就存在一个将Spring Security的顶级Filter注册到Servlet Context的过程。

首先关注javax.servlet.ServletContainerInitializer,该类是tomcat-embed-core包中的类:

// 通过SPI方式导入实现类:
// META-INF/services/javax.servlet.ServletContainerInitializer 
public interface ServletContainerInitializer {/** * Receives notification during startup of a web application of the classes within the web application * that matched the criteria defined via the annotation:* javax.servlet.annotation.HandlesTypes * * 处理javax.servlet.annotation.HandlesTypes注解标注类型的实现类**/void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

该接口实现类由SPI方式导入,我们来到spring-web包中:
在这里插入图片描述
可以看到spring对 该接口的实现类为:org.springframework.web.SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = Collections.emptyList();...// 添加if (webAppInitializerClasses != null) {initializers = new ArrayList<>(webAppInitializerClasses.size());for (Class<?> waiClass : webAppInitializerClasses) {initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass).newInstance());}}...// 排序AnnotationAwareOrderComparator.sort(initializers);// 执行for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}}

SpringServletContainerInitializer中调用了一系列org.springframework.web.WebApplicationInitializer#onStartup

可以看到WebApplicationInitializer 有一系列实现类:
在这里插入图片描述
其中就有Security相关的。到此,以上均为 Spring Web中的内容,Spring Security就是基于以上扩展而来。
接上文,来看看org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer:

public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";...@Overridepublic final void onStartup(ServletContext servletContext) {beforeSpringSecurityFilterChain(servletContext);...insertSpringSecurityFilterChain(servletContext);afterSpringSecurityFilterChain(servletContext);}...}

但是,经过调试发现,Spring Security的Filter注册过程并不是上面的步骤。

重要:
Spring Security 注册Filter 不是通过上文的 javax.servlet.ServletContainerInitializerorg.springframework.web.WebApplicationInitializer#onStartup 而是org.springframework.boot.web.servlet.ServletContextInitializer,来看看ServletContextInitializer的说明:


/*** 不同于WebApplicationInitializer,实现该接口的类(且没有实现WebApplicationInitializer)* 不会被SpringServletContainerInitializer检测到,所以不会由servlet容器自动启动。* 该类的目的和ServletContainerInitializer一样,但是 其中的Servlet的生命周期由Spring控制而不是Servlet容器。*/
@FunctionalInterface
public interface ServletContextInitializer {void onStartup(ServletContext servletContext) throws ServletException;
}

DelegatingFilterProxy

首先来看自动配置类:org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration

@AutoConfiguration(after = SecurityAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
public class SecurityFilterAutoConfiguration {// DEFAULT_FILETER_NAME = "springSecurityFilterChain"private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;// 必须存在名称为springSecurityFilterChain的bean// 名称为springSecurityFilterChain的bean实际上类型即是 org.springframework.security.web.FilterChainProxy@Bean@ConditionalOnBean(name = DEFAULT_FILTER_NAME)public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);registration.setOrder(securityProperties.getFilter().getOrder());registration.setDispatcherTypes(getDispatcherTypes(securityProperties));return registration;}...
}

可以看到DelegatingFilterProxyRegistrationBean被注入Bean容器,且名称为"springSecurityFilterChain"的Bean必须存在,而DelegatingFilterProxyRegistrationBean#getFilter用来获取真正的Security Filter代理类DelegatingFilterProxy,需要注意的是,DelegatingFilterProxy实现了Filter接口。

先来看看DelegatingFilterProxyRegistrationBean的类图结构:

在这里插入图片描述

DelegatingFilterProxyRegistrationBean负责整合Servlet Filter注册(主要就是代理类注册)和Spring生命周期,而真正的代理类DelegatingFilterProxy通过
DelegatingFilterProxyRegistrationBean#getFilter获取。这体现了职责单一的设计原则。

public class DelegatingFilterProxyRegistrationBean ... {...@Overridepublic DelegatingFilterProxy getFilter() {// 创建真正的代理(匿名子类),并具有延迟加载的能力return new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()) {@Overrideprotected void initFilterBean() throws ServletException {// Don't initialize filter bean on init()}};}...
}

接下来,DelegatingFilterProxyRegistrationBean中的DelegatingFilterProxy需要完成对多个SecurityFilterChain的代理。而这个代理过程Security又通过一个代理类org.springframework.security.web.FilterChainProxy完成 。意思是,DelegatingFilterProxy是整个Security的代理,而FilterChainProxy是SecurityFilterChain的代理,且DelegatingFilterProxy是通过FilterChainProxy来完成代理的(代理一个代理)。

来看看DelegatingFilterProxy

public class DelegatingFilterProxy extends GenericFilterBean {// 就是 springSecurityFilterChain,代表FilterChainProxy的beanName@Nullableprivate String targetBeanName;// 代理的FilterChainProxy@Nullableprivate volatile Filter delegate;...@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)throws ServletException, IOException {// Lazily initialize the delegate if necessary.Filter delegateToUse = this.delegate;if (delegateToUse == null) {synchronized (this.delegateMonitor) {delegateToUse = this.delegate;if (delegateToUse == null) {...// 初始化代理类delegateToUse = initDelegate(wac);}this.delegate = delegateToUse;}}// Let the delegate perform the actual doFilter operation.invokeDelegate(delegateToUse, request, response, filterChain);}...protected Filter initDelegate(WebApplicationContext wac) throws ServletException {String targetBeanName = getTargetBeanName();// 容器中获取名称为springSecurityFilterChain 类型为Filter的bean// 即 FilterChainProxy// 所以 注册 DelegatingFilterProxyRegistrationBean 时必须有 @ConditionalOnBean(name="springSecurityFilterChain")Filter delegate = wac.getBean(targetBeanName, Filter.class);...return delegate;}
}

上文说到,在注册DelegatingFilterProxyRegistrationBean的自动配置类中 必须要有springSecurityFilterChain名称的bean存在,而这个名称为springSecurityFilterChain的bean实际上类型即是 org.springframework.security.web.FilterChainProxy

整个流程如下:
在这里插入图片描述
有点像 道生一,一生二,二生三,三生万物 的思想,我将它命名为 道德经设计模式,嘿嘿 。
那么FilterChainProxy又是在哪儿注入的呢?

FilterChainProxy

在配置类org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration中我们可以发现,这里注入了FilterChainProxy

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {...private WebSecurity webSecurity;// 多个SecurityFilterChainprivate List<SecurityFilterChain> securityFilterChains = Collections.emptyList();// 多个WebSecurityCustomizerprivate List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList();...// 注入一个Filter,指定名称为springSecurityFilterChain@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)public Filter springSecurityFilterChain() throws Exception {...for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);// 为每个SecurityFilterChain中的每个Filter添加拦截方法for (Filter filter : securityFilterChain.getFilters()) {if (filter instanceof FilterSecurityInterceptor) {this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);break;}}}// 自定义器对每个SecurityFilterChain均生效for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {customizer.customize(this.webSecurity);}// 这里build()方法返回  org.springframework.security.web.FilterChainProxyreturn this.webSecurity.build();}...// 自动注入, 通常我们需要自定义的就是这个SecurityFilterChain类型// 只需要在业务配置类中注册一个SecurityFilterChain类型的bean就能被注入到这里@Autowired(required = false)void setFilterChains(List<SecurityFilterChain> securityFilterChains) {this.securityFilterChains = securityFilterChains;}// 自动注入@Autowired(required = false)void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {this.webSecurityCustomizers = webSecurityCustomizers;}}

在业务配置类中,我们可以自定义SecurityFilterChainWebSecurityCustomizer的bean,配置如下:

@Configuration
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf().disable();// 必须显式注明,配合CorsConfigurationSource的Bean,不然即使在web里面配置了跨域,security这里依然会cors errorhttp.cors();http.authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll().anyRequest().authenticated();http.formLogin().successHandler(loginSuccessHandler);http.oauth2Login().successHandler(giteeSuccessHandler);http.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler);http.addFilterBefore(bearAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");}
}

OK,我们再来看看 org.springframework.security.web.FilterChainProxy:

public class FilterChainProxy extends GenericFilterBean {private List<SecurityFilterChain> filterChains;private HttpFirewall firewall = new StrictHttpFirewall();@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {...doFilterInternal(request, response, chain);...}private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {// 转化为org.springframework.security.web.firewall.FirewalledRequest// reject potentially dangerous requests and/or wrap them to control their behaviour.FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);// #getFilters会在所有SecurityFilterChain中进行匹配List<Filter> filters = getFilters(firewallRequest);...// 转化为 VirtualFilterChain// VirtualFilterChain是FilterChainProxy内部静态类VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);// 开启 SecurityFilterChain中所有filter过程virtualFilterChain.doFilter(firewallRequest, firewallResponse);}private List<Filter> getFilters(HttpServletRequest request) {for (SecurityFilterChain chain : this.filterChains) {// 返回第一个符合规则的SecurityFilterChainif (chain.matches(request)) {return chain.getFilters();}}return null;}/*** 执行额外的 filters,控制filters执行过程* 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;// 该SecurityFilterChain中所有filter的数量private final int size;// 当前filter的位置private int currentPosition = 0;...@Overridepublic void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {if (this.currentPosition == this.size) {// 执行完毕// Deactivate path stripping as we exit the security filter chainthis.firewalledRequest.reset();this.originalChain.doFilter(request, response);return;}// 继续执行filterChain中下一个filterthis.currentPosition++;Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);nextFilter.doFilter(request, response, this);}...}...
}

Filters

按顺序排序,Spring Security内置了以下Filter:

  • ForceEagerSessionCreationFilter
  • ChannelProcessingFilter
  • WebAsyncManagerIntegrationFilter
  • SecurityContextPersistenceFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • ConcurrentSessionFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter : allows translation of AccessDeniedException and AuthenticationException into HTTP responses
  • FilterSecurityInterceptor
  • SwitchUserFilter

http://www.ngui.cc/article/show-861242.html

相关文章

MYSQL常用工具

1、字符串截取 substring(INDEX_NAME, 3, 2) -----------INDEX_NAME 2、String 转 Int CAST(INDEX_NAME AS SIGNED integer) ------------INDEX_NAME 3、时间格式化 date_format(INDEX_NAME, ‘%Y-%m-%d %H:%i:%s’) ----------------INDEX_NAME 4、IFNULL()、CASE-WHEN …

关于splitChunks的一次原理探索

前言 前端时间在做项目加载优化时用到了splitChunks自动拆包&#xff0c;后了解了一下原理写下了此文。 Modules和Chunks Modules简单来理解就是我们写的功能模块&#xff0c;不管是CommonJS还是ESM都算是一个Module&#xff0c;而Chunks则是webpack根据我们的规则/默认规则…

贝克制药冲刺上市:资产负债率高,抗乙肝制剂产品收入和占比下滑

2月3日&#xff0c;安徽贝克制药股份有限公司&#xff08;下称“贝克制药”&#xff09;在上海证券交易所递交招股书&#xff0c;准备在科创板上市&#xff0c;国元证券为其保荐机构。 本次冲刺上市&#xff0c;贝克制药计划募资14.20亿元&#xff0c;其中5.32亿元用于年产单方…

矿山安全生产监测预警系统 opencv

矿山安全生产监测预警系统通过pythonopencv网络模型计算机视觉技术&#xff0c;对现场画面中人的不安全行为”、“物的不安全状态”、“环境的不安全因素”三方面出发进行实时监测&#xff0c;当监测到现场画面中人员未穿反光衣行为、明火烟雾、未穿安全帽行为、矿车掉道识别、…

Mybatis一对多以及多对一

场景&#xff1a; 多个学生对应一个老师 如果对于学生这边&#xff0c;就是一个多对一的现象&#xff0c;即从学生这边关联一个老师&#xff01; 数据库设计 CREATE TABLE teacher (id INT(10) NOT NULL,name VARCHAR(30) DEFAULT NULL,PRIMARY KEY (id) ) ENGINEINNODB DEF…

高质量有效编程笔记

来自李云的高质量有效编程电子书的摘抄&#xff0c;有些需要进一步实践 高质量有效编程笔记&#xff1a; 硬件部分 1、 微处理器、微控制器&#xff0c;从编程角度无区别。 2、 寄存器&#xff1a;通用寄存器GPR、浮点寄存器FPR 3、 通用寄存器GPR&#xff1a;执行指令、整数运…

Kaggle系列之预测泰坦尼克号人员的幸存与死亡(随机森林模型)

Kaggle是开发商和数据科学家提供举办机器学习竞赛、托管数据库、编写和分享代码的平台&#xff0c;本节是对于初次接触的伙伴们一个快速了解和参与比赛的例子&#xff0c;快速熟悉这个平台。当然提交预测结果需要注册&#xff0c;这个可能需要科学上网了。我们选择一个预测的入…

最新整理Spring面试题2023

Spring面试专题 1.Spring应该很熟悉吧&#xff1f;来介绍下你的Spring的理解 有些同学可能会抢答&#xff0c;不熟悉!!! 好了&#xff0c;不开玩笑&#xff0c;面对这个问题我们应该怎么来回答呢&#xff1f;我们给大家梳理这个几个维度来回答 1.1 Spring的发展历程 先介绍…

【嵌入式】MDK使用sct文件将代码段放入RAM中执行

sct文件即分散加载文件&#xff0c;是ARMCC编译器使用的链接脚本文件&#xff0c;等同于GCC编译器的ld链接脚本。MDK IDE使用的是ARMCC。 支持NorFlash中运行代码&#xff08;XIP&#xff09;的MCU例如STM32&#xff0c;一般将所有代码&#xff08;text段&#xff09;都放在FL…

Word控件Spire.Doc 【Table】教程(10): 如何在 C#、VB.NET 中将嵌入式 Excel 工作表转换为 Word 表格

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…