鬆哥原創的 Spring Boot 視頻教程已經殺青,感興趣的小夥伴戳這裏-->Spring Boot+Vue+微人事視頻教程java
前面咱們對 Spring Security 源碼的講解都比較零散,今天鬆哥試着來和你們捋一遍 Spring Security 的初始化流程,順便將前面的源碼解析文章串起來。web
Spring Security 啓動流程並不難,可是因爲涉及到的知識點很是龐雜,因此鬆哥在以前已經連載過好幾篇源碼解讀的文章了,你們把這些源碼解讀的文章搞懂了,今天這篇文章就好理解了。spring
在 Spring Boot 中,Spring Security 的初始化,咱們就從自動化配置開始分析吧!微信
1.SecurityAutoConfiguration
Spring Security 的自動化配置類是 SecurityAutoConfiguration,咱們就從這個配置類開始分析。app
@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
接着來看上面出現的 WebSecurityEnablerConfiguration:編輯器
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
}
這個配置倒沒啥可說的,給了一堆生效條件,最終給出了一個 @EnableWebSecurity 註解,看來初始化重任落在 @EnableWebSecurity 註解身上了。ide
3.@EnableWebSecurity
@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 所作的事情,有兩件比較重要:post
-
導入 WebSecurityConfiguration 配置。 -
經過 @EnableGlobalAuthentication 註解引入全局配置。
3.1 WebSecurityConfiguration
WebSecurityConfiguration 類實現了兩個接口,咱們來分別看下:學習
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
}
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 實現的兩個接口。
在 WebSecurityConfiguration 內部定義的 Bean 中,最爲重要的是兩個方法:
-
springSecurityFilterChain 該方法目的是爲了獲取過濾器鏈。 -
setFilterChainProxySecurityConfigurer 這個方法是爲了收集配置類並建立 WebSecurity。
這兩個方法是核心,咱們來逐一分析,先來看 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 容器中(參見深刻理解 SecurityConfigurer 【源碼篇】)。
第二個參數 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 實例(多個過濾器鏈,參見:Spring Security 居然能夠同時存在多個過濾器鏈?),那麼咱們確定要經過 @Order 註解對其進行排序,以便分出一個優先級出來,並且這個優先級還不能相同。
因此接下來的 for 循環中就是判斷這個優先級是否有相同的,要是有,直接拋出異常。
最後,遍歷 webSecurityConfigurers,並將其數據挨個配置到 webSecurity 中。webSecurity.apply 方法會將這些配置存入 AbstractConfiguredSecurityBuilder.configurers 屬性中(參見:深刻理解 HttpSecurity【源碼篇】)。
這就是 setFilterChainProxySecurityConfigurer 方法的工做邏輯,你們看到,它主要是在構造 WebSecurity 對象。
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() 方法了,這個方法的調用就是去構建過濾器鏈了。
根據 深刻理解 HttpSecurity【源碼篇】 一文的介紹,這個 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 的初始化工做,具體參考:深刻理解 WebSecurityConfigurerAdapter【源碼篇】。init 方法在執行時,會涉及到 HttpSecurity 的初始化,而 HttpSecurity 的初始化,須要配置 AuthenticationManager,因此這裏最終還會涉及到一些全局的 AuthenticationManagerBuilder 及相關屬性的初始化,具體參見:深刻理解 AuthenticationManagerBuilder 【源碼篇】,須要注意的是,AuthenticationManager 在初始化的過程當中,也會來到這個 doBuild 方法中,具體參考鬆哥前面文章,這裏就再也不贅述。
configure 方法會遍歷全部的 WebSecurityConfigurerAdapter ,並執行其 configure 方法。WebSecurityConfigurerAdapter#configure 方法默認是一個空方法,開發者能夠本身重寫該方法去定義本身的 WebSecurity,具體參考:深刻理解 WebSecurityConfigurerAdapter【源碼篇】。
最後調用 performBuild 方法進行構建,這個最終執行的是 WebSecurity#performBuild 方法,該方法執行流程,參考鬆哥前面文章深刻理解 WebSecurityConfigurerAdapter【源碼篇】。
performBuild 方法執行的過程,也是過濾器鏈構建的過程。裏邊會調用到過濾器鏈的構建方法,也就是默認的十多個過濾器會挨個構建,這個構建過程也會調用到這個 doBuild 方法。
performBuild 方法中將成功構建 FilterChainProxy,最終造成咱們須要的過濾器鏈。
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 【源碼篇】一文。
4.小結
這即是 Spring Security 的一個大體的初始化流程。大部分的源碼在前面的文章中都講過了,本文主要是是一個梳理,若是小夥伴們還沒看前面的文章,建議看過了再來學習本文哦。
今日干貨
公衆號後臺回覆 ssm,免費獲取鬆哥純手敲的 SSM 框架學習乾貨。
本文分享自微信公衆號 - 江南一點雨(a_javaboy)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。