源码解读Spring-Security之初始化启动

12 篇文章 2 订阅
10 篇文章 1 订阅

基于 SpringBoot2.4.2 + Spring-Security5.4.2。

1. 前言

对于Java Web应用开发,用到的安全框架通常就是在Shiro 和Spring-Security 中二选一。过往笔者选择的是 Shiro ,鉴于最近两年使用到的技术栈中Spring的权重越来越高,加之近期所参与的一个项目需求,遂决定加深以下对于Spring-Security的理解,做到"胸有成竹,遇事不慌"。

2. 测试用例

本次相关的测试用例源码笔者一并上传到了码云,部分源码摘抄自底部第一条参考链接,本文更关注于底层实现逻辑,不对这些入门注意事项再拾人牙慧。

得益于SpringBoot强大的封装,相关的配置性代码相当少,再机上SpringBoot官方提供的 Spring Initializr,更是进一步降低了初学者入门的门槛,吸引大量对某项技术感兴趣的技术人员 ,让SpringBoot进入一个滚雪球式的发展轨迹,这也给我们这类喜好阅读源码多了解一些细节的偏执狂一点提醒:“阅读源码时也是需要斟酌的,尽量挑一些流行,受众更广的技术栈来研究,阅读源码这类苦差事肯定得尽量挑一些高ROI的来做,否则怎么对得起自己”。

3. 源码解读

接下来让我们正式开始我们的源码解读之旅,作为本次旅程的起步,首先让我们看看SpringBoot对于Spring-Security的支持,也就是SpringBoot强大的AutoConfiguration

我们直接将目光定位到 spring-boot-autoconfigure-2.4.2.jar 中的META-INF/spring.factories文件,从中确定如下相关类:

  1. SecurityAutoConfiguration
  2. UserDetailsServiceAutoConfiguration
  3. SecurityFilterAutoConfiguration

接下来我们对这三个类的作用进行逐一介绍。

3.1 SecurityFilterAutoConfiguration

该配置类最大的用途是向Spring容器中注入一个占位Filter(DelegatingFilterProxyRegistrationBean),且为该占位Filter提供底层支撑的是被锁死为名为"springSecurityFilterChain"的Spring容器Bean。记住这个名称,之后我们会多次遇到它。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
// 明确告知SpringBoot加载顺序
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {

	private static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

	@Bean
	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
			SecurityProperties securityProperties) {
		DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
				DEFAULT_FILTER_NAME);
		registration.setOrder(securityProperties.getFilter().getOrder());
		...
		return registration;
	}
}

相应的堆栈(Servlet容器视野):StandardContext.filterStart()
在这里插入图片描述

3.2 SecurityAutoConfiguration

一眼看上去,本类做了两件事:

  1. 直接向容器中注入AuthenticationEventPublisher接口实现类DefaultAuthenticationEventPublisher,作为框架观察者模式的实现,这样其它有需要的组件可以直接引用。
  2. 借助@Import注解向Spring容器中注入:
    1. SpringBootWebSecurityConfiguration 。内部类,借助Spring容器中已存在的HttpSecurity向容器中注入一个SecurityFilterChain实现类。(记住它,之后我们会提到)
    2. WebSecurityEnablerConfiguration。内部类,主要用于引入注解@EnableWebSecurity。启用时机为容器中缺少一个名为springSecurityFilterChain的Bean。(再次遇到这个名称)
    3. SecurityDataConfiguration。用于向Spring容器中注入一个SecurityEvaluationContextExtension实例。(注意:本例中并不会引入该Bean)

@EnableWebSecurity注解
关于本注解,牵扯的东西还不少,我们直接观察其定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 以下引入的四个配置类
// 1. WebSecurityConfiguration
// 2. SpringWebMvcImportSelector 内部类, 向Spring容器中注入一个 WebMvcSecurityConfiguration 实例。
// 3. OAuth2ImportSelector 内部类, 根据当前classpath下引入的依赖, 决定与Oauth2相关的Bean引入.
// 4. HttpSecurityConfiguration  内部类, 用于构建一个HttpSecurity实例,进而构建出一个SecurityFilterChain实现类.
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
		HttpSecurityConfiguration.class })
@EnableGlobalAuthentication  // 关于这个注解的说明我们留到下篇博文
@Configuration
public @interface EnableWebSecurity {

	// 通过启用该属性,我们可以直观感受到Spring-Security是如何处理我们的请求的. 
	// 学习阶段强烈推荐开启.
	/**
	 * Controls debugging support for Spring Security. Default is false.
	 * @return if true, enables debug support with Spring Security
	 */
	boolean debug() default false;

}

关于以上@EnableWebSecurity注解引入的配置类,本次我们主要分析以下三个:

  1. WebSecurityConfiguration
    主要职责是借助WebSecurity创建一个FilterChainProxy实例(该实例间接是实现了 javax.servlet.Filter接口,且该实例在Spring容器中的名称为springSecurityFilterChain,这里就和上面的3.1小节呼应上了)。相关代码如下:

    // WebSecurityConfiguration.springSecurityFilterChain()
    /**
     * 返回值将以 Servlet Filter的角色并入到Servlet执行流程中, 当请求到达该Filter时, Spring-Security提供的一系列功能才算正式开始生效.
     * Creates the Spring Security Filter Chain (Spring-Security FilterChain)
     * @return the {@link Filter} that represents the security filter chain
     * @throws Exception
     */
    // 仔细观察这里的Bean名, 是不是很熟悉, 不过还没完, 一会我们还会看到它.
    @Bean(name = "springSecurityFilterChain") 
    public Filter springSecurityFilterChain() throws Exception {
    	...
    
        // 这里的SecurityFilterChain接口名称是不是也很熟悉
    	for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
    		this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
    		for (Filter filter : securityFilterChain.getFilters()) {
    			if (filter instanceof FilterSecurityInterceptor) {
    				this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
    				break;
    			}
    		}
    	}
    	
    ... 
    
    return this.webSecurity.build();
    }
    
  2. WebMvcSecurityConfiguration
    本配置类主要是针对中的SpringMVC中的Controller层的参数检索,能够处理如下三类参数:

    1. 被注解AuthenticationPrincipal修饰的方法参数。相应处理类为:AuthenticationPrincipalArgumentResolver
    2. 被注解CurrentSecurityContext修饰的方法参数。相应处理类为:CurrentSecurityContextArgumentResolver
    3. 类型为CsrfToken的方法参数。相应处理类为:CsrfTokenArgumentResolver
  3. HttpSecurityConfiguration
    本配置类的主要职责是向Spring容器中注入一个HttpSecurity实例(注意的是该Bean的Scope为"prototype")。该实例将作为方法参数传入到我们前面提到的SpringBootWebSecurityConfiguration(负责构建注入到Spring容器中的SecurityFilterChain实例Bean)的唯一方法中,闭环形成

3.3 UserDetailsServiceAutoConfiguration

本类的作用是向Spring容器中注入一个UserDetailsService实现类,默认为基于内存的InMemoryUserDetailsManager,相关的配置项从配置类SecurityProperties中来(这也是为什么默认情况下我们登录密码看着和乱码一样,就是因为SecurityProperties默认使用UUID当作密码)。关于UserDetailsServiceUserDetails我们以后有机会再说。

4. 总结一下

SpringBoot + Spring-Security的组合下:

  1. 程序启动的初始阶段,借助SpringBoot的AutoConfiguration向容器中注入DelegatingFilterProxyRegistrationBeanHttpSecuritySecurityFilterChainFilterChainProxy实例。其中的依赖关系如下:
Bean依赖于配置类备注
DelegatingFilterProxyRegistrationBean隐式依赖于名为springSecurityFilterChain的BeanSecurityFilterAutoConfiguration该Bean的主要任务是在Servlet容器层面占据一个Filter位,等待请求逻辑到来,并引导请求进入到Spring-Security的处理体系下
HttpSecurityHttpSecurityConfiguration该类的继承链已经暗示了其职责是创建SecurityFilterChain的Builder。
SecurityFilterChainHttpSecuritySpringBootWebSecurityConfiguration该接口实现类封装了一类请求+相应的Filter集合
FilterChainProxySecurityFilterChainWebSecurityConfiguration该Bean在Spring容器中的名称被锁死为springSecurityFilterChain
  1. 作为ServletContextInitializer接口实现类的DelegatingFilterProxyRegistrationBean,SpringBoot会确保其作为Servlet Filter并入到Servlet容器的生命周期下。
  2. 启动完毕,等待接收用户请求到达DelegatingFilterProxyRegistrationBean代表的Servlet Filter。
  3. 结构图如下: ProcessOn

5. 最佳实践

  1. 学习过程中通过开启 @EnableWebSecurity(debug = true)将大幅降低学习曲线。
  2. 内部类org.springframework.security.config.annotation.web.builders.FilterComparator能够让你快速了解Spring-Security中内置的Filter有哪些,以及它们的组合顺序。

6. Links

  1. Spring Security小教程
  2. 【spring boot 系列】spring security 实践 + 源码分析
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值