基于 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
文件,从中确定如下相关类:
SecurityAutoConfiguration
UserDetailsServiceAutoConfiguration
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
一眼看上去,本类做了两件事:
- 直接向容器中注入
AuthenticationEventPublisher
接口实现类DefaultAuthenticationEventPublisher
,作为框架观察者模式的实现,这样其它有需要的组件可以直接引用。 - 借助
@Import
注解向Spring容器中注入:SpringBootWebSecurityConfiguration
。内部类,借助Spring容器中已存在的HttpSecurity
向容器中注入一个SecurityFilterChain
实现类。(记住它,之后我们会提到)WebSecurityEnablerConfiguration
。内部类,主要用于引入注解@EnableWebSecurity
。启用时机为容器中缺少一个名为springSecurityFilterChain
的Bean。(再次遇到这个名称)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
注解引入的配置类,本次我们主要分析以下三个:
-
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(); }
-
WebMvcSecurityConfiguration
本配置类主要是针对中的SpringMVC中的Controller层的参数检索,能够处理如下三类参数:- 被注解
AuthenticationPrincipal
修饰的方法参数。相应处理类为:AuthenticationPrincipalArgumentResolver
。 - 被注解
CurrentSecurityContext
修饰的方法参数。相应处理类为:CurrentSecurityContextArgumentResolver
。 - 类型为
CsrfToken
的方法参数。相应处理类为:CsrfTokenArgumentResolver
。
- 被注解
-
HttpSecurityConfiguration
本配置类的主要职责是向Spring容器中注入一个HttpSecurity
实例(注意的是该Bean的Scope为"prototype")。该实例将作为方法参数传入到我们前面提到的SpringBootWebSecurityConfiguration
(负责构建注入到Spring容器中的SecurityFilterChain
实例Bean)的唯一方法中,闭环形成。
3.3 UserDetailsServiceAutoConfiguration
本类的作用是向Spring容器中注入一个UserDetailsService
实现类,默认为基于内存的InMemoryUserDetailsManager
,相关的配置项从配置类SecurityProperties
中来(这也是为什么默认情况下我们登录密码看着和乱码一样,就是因为SecurityProperties
默认使用UUID当作密码)。关于UserDetailsService
和UserDetails
我们以后有机会再说。
4. 总结一下
SpringBoot + Spring-Security的组合下:
- 程序启动的初始阶段,借助SpringBoot的AutoConfiguration向容器中注入
DelegatingFilterProxyRegistrationBean
,HttpSecurity
,SecurityFilterChain
,FilterChainProxy
实例。其中的依赖关系如下:
Bean | 依赖于 | 配置类 | 备注 |
---|---|---|---|
DelegatingFilterProxyRegistrationBean | 隐式依赖于名为springSecurityFilterChain 的Bean | SecurityFilterAutoConfiguration | 该Bean的主要任务是在Servlet容器层面占据一个Filter位,等待请求逻辑到来,并引导请求进入到Spring-Security的处理体系下 |
HttpSecurity | 无 | HttpSecurityConfiguration | 该类的继承链已经暗示了其职责是创建SecurityFilterChain 的Builder。 |
SecurityFilterChain | HttpSecurity | SpringBootWebSecurityConfiguration | 该接口实现类封装了一类请求+相应的Filter集合 |
FilterChainProxy | SecurityFilterChain | WebSecurityConfiguration | 该Bean在Spring容器中的名称被锁死为springSecurityFilterChain |
- 作为
ServletContextInitializer
接口实现类的DelegatingFilterProxyRegistrationBean
,SpringBoot会确保其作为Servlet Filter并入到Servlet容器的生命周期下。 - 启动完毕,等待接收用户请求到达
DelegatingFilterProxyRegistrationBean
代表的Servlet Filter。 - 结构图如下: ProcessOn
5. 最佳实践
- 学习过程中通过开启
@EnableWebSecurity(debug = true)
将大幅降低学习曲线。 - 内部类
org.springframework.security.config.annotation.web.builders.FilterComparator
能够让你快速了解Spring-Security中内置的Filter有哪些,以及它们的组合顺序。