前面对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@Configurationpublic @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中定义了该实例:
它的@Beanpublic 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方法中执行的。
这里会记录下来整个项目的构建状态。比较重要的方法,@Overrideprotected 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)@Configurationpublic @interface EnableGlobalAuthentication {}
AuthenticationConfiguration配置,该配置在深入理解AuthenticationManagerBuilder(源码篇)中已经介绍过了。
