前面对Spring Security 源码的讲解都比较零散,今天捋一遍Spring Security的初始化流程,顺便将前面的源码分析文章串起来。
1、SecurityAutoConfiguraion
分析一个SpringBoot的stater,都是从xxxAutoConfiguration
类开始的。而Spring Security 的自动化配置类是SecurityAutoConfiguration
,所以就从这个自动配置类开始分析:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
这个Bean中,定义了一个事件发布器。另外导入了三个配置:
SpringBootWebSecurityConfiguration
这个配置的作用是如果开发者没有自定义WebSecurityConfigurerAdapter
的话,这里提供一个默认的实现。WebSecurityEnablerConfiguration
这个配置是Spring Security的核心配置,也将是我们分析的重点。SecurityDataConfiguration
提供了Spring Security整合Spring Data的支持,由于国内使用mybatis较多,所以这个配置发光发热的场景有限。2、WebSecurityEnablerConfiguration
```java @Configuration(proxyBeanMethods = false) @ConditionalOnBean(WebSecurityConfigurerAdapter.class) @ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @EnableWebSecurity public class WebSecurityEnablerConfiguration {
}
这个配置没啥说的,给了一堆生效条件,最终给出一个`@EnableWebSecurity`注解,看来初始化的重任落在<br />`@EnableWebSecurity`注解身上。
<a name="Kts17"></a>
# 3、@EnableWebSecurity
```java
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
@EnableWebSecurity
注解所做的事情,有两件比较重要:
- 导入
WebSecurityConfiguration
配置。 通过
@EnableGlobalAuthentication
注解引入全局配置。3.1、WebSecurityConfiguration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
}
WebSecurityConfiguration
实现了两个接口,其中ImportAware
接口和@Import
注解一起使用的。实现了ImportAware
接口的配置类可以方便的通过setImportMetadata
方法获取到导入类中的数据配置。有点绕,梳理下,就是WebSecurityConfiguration
实现了ImportAware
接口,使用@Import
注解在@EnableWebSecurity
上导入WebSecurityConfiguration
之后,在WebSecurityConfiguration
的setImportMetadata
方法中可以方便的获取到@EnableWebSecurity
中的属性,这里主要是debug
属性。
来看下WebSecurityConfiguration#setImportMetadata
方法:public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> enableWebSecurityAttrMap = importMetadata
.getAnnotationAttributes(EnableWebSecurity.class.getName());
AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes
.fromMap(enableWebSecurityAttrMap);
debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
if (webSecurity != null) {
webSecurity.debug(debugEnabled);
}
}
获取到
debug
属性赋值给webSecurity
。
实现BeanClassLoaderAware
接口则是为了方便的获取ClassLoader
。
🎯在WebSecurityConfiguration
内部定义的Bean中,最为重要的两个:setFilterChainProxySecurityConfigurer
方法则是为了收集配置类并创建webSecurity
。springSecurityFilterChain
该方法的目的是为了使用webSecurity
构建FilterChainProxy
。3.1.1、setFilterChainProxySecurityConfigurer
首先这个方法有两个参数,两个参数都会进行注入。@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
第一个参数objectPostProcessor
是一个后置处理器,默认的实现是AutowireBeanFactoryObjectPostProcessor
,主要是为了将new
出来的对象注入到Spring
容器中。
第二个参数方法webSecurityConfigurers
是一个集合,这个集合里存放的都是SecurityConfigurer
,前面分析过的过滤器链中过滤器的配置器,包括WebSecurityConfigurerAdapter
的子类,都是SecurityConfigurer
的实现类。根据@value
注解的描述,我们可以知道,这个集合中的数据来自autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()
方法。
在WebSecurityConfiguration
中定义了该实例:
它的@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
getWebSecurityConfigurers
方法来看下:
可以看到,其实就是从public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
beanFactory
工厂中查询到WebSecurityConfigurer
的实例返回。WebSecurityConfigurer
的实例其实就是WebSecurityConfigurerAdapter
,如果没有自定义WebSecurityConfigurerAdapter
,那么默认使用的就是SpringBootWebSecurityConfiguration
中自定义的WebSecurityConfigurerAdapter
。
当然我们可能也自定义了WebSecurityConfigurerAdapter
,而且如果我们配置了多个过滤器链(多个HttpSecurity
配置),那么WebSecurityConfigurerAdapter
的实例也将有多个。所以这里返回的是List
集合。
至此,我们搞明白了setFilterChainProxySecurityConfigurer
方法的两个参数,回到该方法继续分析。
接下来创建了webSecurity
对象,并且放到objectPostProcessor
中处理了一下,也就是new
出来的对象存入到Spring
容器中。
调用webSecurityConfigurers.sort
方法对WebSecurityConfigurerAdapter
进行排序,如果我们配置了多个WebSecurityConfigurerAdapter
实例(多个过滤器链),那么我们要通过@Order
注解对其进行排序,以便分出一个优先级,而且这个优先级还不能相同。所以接下来的for
循环中就是判断这个优先级是否相同的,要是有,直接抛出异常。
最后,遍历webSecurityConfigurers
,并将其数据挨个配置到webSecurity
中。webSecurity.apply
方法会将这些配置存入AbstractConfiguredSecurityBuilder.configurers
属性中。
这就是setFilterChainProxySecurityConfigurer
方法的逻辑,可以看到,它主要是在构造WebSecurity
对象。3.1.2、springSecurityFilterChain
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();
}
webSecurityConfigurers
,一般来说都是有的,即使你没有配置,还有一个默认的。当然,如果不存在的话,这里会现场new
一个出来,然后调用apply
方法。
🎨最最关键的就是最后的webSecurity.build
方法了,这个方法的调用就是去构建过滤器链了。
根据深入理解Spring Security配置与构建(源码篇)一文中的介绍,这个build
方法最终是在AbstractConfiguredSecurityBuilder#dobuild
方法中执行的。
这里会记录下来整个项目的构建状态。比较重要的方法,@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
init
,configure
和performBuild
方法。init
方法会遍历所有的WebSecurityConfigurerAdapter
,并执行其init
方法。WebSecurityConfigurerAdapter#init
方法主要是做HttpSecurity
的初始化工作,init
方法在执行时,会涉及到HttpSecurity
的初始化,而HttpSecurity
的初始化需要配置AuthenticationManager
,所以这里最终还会涉及到一些全局的AuthenticationManagerBuilder
及相关属性的初始化,具体可参考深入理解AuthenticationManagerBuilder(源码篇),需要注意的是,AuthenticationManager
初始化的时候也会来到这个doBuild
方法中。configure
方法会遍历所有的WebSecurityConfigurerAdapter
,并执行其configure
方法。WebSecurityConfigurerAdapter#configure
方法默认是一个空方法,开发者可以自己重写该方法定义自己的WebSecurity
。
最后调用performBuild
方法进行构建,这个最终执行的是WebSecurity#performBuild
方法,WebSecurity#performBuild
方法执行的过程,也是过滤器链构建的过程。里面会调用到过滤器链的构建方法,也就是默认的十多个过滤器会挨个构建,这个构建过程也会调用到这个doBuild
方法。3.2、@EnableGlobalAuthentication
@EnableWebSecurity
注解除了过滤器链的构建,还有一个而注解就是@EnableGlobalAuthentication
。
可以看到,该注解的主要作用就是导入@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
AuthenticationConfiguration
配置,该配置在深入理解AuthenticationManagerBuilder(源码篇)中已经介绍过了。