您好,登錄后才能下訂單哦!
怎樣進(jìn)行Spring Security初始化流程梳理,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
今天試著來和大家捋一遍 Spring Security 的初始化流程。
在 Spring Boot 中,Spring Security 的初始化,我們就從自動(dòng)化配置開始分析吧!
Spring Security 的自動(dòng)化配置類是 SecurityAutoConfiguration,我們就從這個(gè)配置類開始分析。
@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);
}
}
這個(gè) Bean 中,定義了一個(gè)事件發(fā)布器。另外導(dǎo)入了三個(gè)配置:
接著來看上面出現(xiàn)的 WebSecurityEnablerConfiguration:
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
}
這個(gè)配置倒沒啥可說的,給了一堆生效條件,最終給出了一個(gè) @EnableWebSecurity 注解,看來初始化重任落在 @EnableWebSecurity 注解身上了。
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth3ImportSelector.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 類實(shí)現(xiàn)了兩個(gè)接口,我們來分別看下:
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
}
ImportAware 接口和 @Import 注解一起使用的。實(shí)現(xiàn)了 ImportAware 接口的配置類可以方便的通過 setImportMetadata 方法獲取到導(dǎo)入類中的數(shù)據(jù)配置。
可能有點(diǎn)繞,我再梳理下,就是 WebSecurityConfiguration 實(shí)現(xiàn)了 ImportAware 接口,使用 @Import 注解在 @EnableWebSecurity 上導(dǎo)入 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。
實(shí)現(xiàn) BeanClassLoaderAware 接口則是為了方便的獲取 ClassLoader。
這是 WebSecurityConfiguration 實(shí)現(xiàn)的兩個(gè)接口。
在 WebSecurityConfiguration 內(nèi)部定義的 Bean 中,最為重要的是兩個(gè)方法:
這兩個(gè)方法是核心,我們來逐一分析,先來看 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;
}
首先這個(gè)方法有兩個(gè)參數(shù),兩個(gè)參數(shù)都會自動(dòng)進(jìn)行注入,第一個(gè)參數(shù) ObjectPostProcessor 是一個(gè)后置處理器,默認(rèn)的實(shí)現(xiàn)是 AutowireBeanFactoryObjectPostProcessor,主要是為了將 new 出來的對象注入到 Spring 容器中(參見深入理解 SecurityConfigurer 【源碼篇】)。
第二個(gè)參數(shù) webSecurityConfigurers 是一個(gè)集合,這個(gè)集合里存放的都是 SecurityConfigurer,我們前面分析的過濾器鏈中過濾器的配置器,包括 WebSecurityConfigurerAdapter 的子類,都是 SecurityConfigurer 的實(shí)現(xiàn)類。根據(jù) @Value 注解中的描述,我們可以知道,這個(gè)集合中的數(shù)據(jù)來自 autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers() 方法。
在 WebSecurityConfiguration 中定義了該實(shí)例:
@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;
}
可以看到,其實(shí)就是從 beanFactory 工廠中查詢到 WebSecurityConfigurer 的實(shí)例返回。
WebSecurityConfigurer 的實(shí)例其實(shí)就是 WebSecurityConfigurerAdapter,如果我們沒有自定義 WebSecurityConfigurerAdapter,那么默認(rèn)使用的是 SpringBootWebSecurityConfiguration 中自定義的 WebSecurityConfigurerAdapter。
當(dāng)然我們也可能自定義了 WebSecurityConfigurerAdapter,而且如果我們配置了多個(gè)過濾器鏈(多個(gè) HttpSecurity 配置),那么 WebSecurityConfigurerAdapter 的實(shí)例也將有多個(gè)。所以這里返回的是 List 集合。
至此,我們搞明白了了 setFilterChainProxySecurityConfigurer 方法的兩個(gè)參數(shù)。回到該方法我們繼續(xù)分析。
接下來創(chuàng)建了 webSecurity 對象,并且放到 ObjectPostProcessor 中處理了一下,也就是把 new 出來的對象存入 Spring 容器中。
調(diào)用 webSecurityConfigurers.sort 方法對 WebSecurityConfigurerAdapter 進(jìn)行排序,如果我們配置了多個(gè) WebSecurityConfigurerAdapter 實(shí)例(多個(gè)過濾器鏈,參見:Spring Security 竟然可以同時(shí)存在多個(gè)過濾器鏈?),那么我們肯定要通過 @Order 注解對其進(jìn)行排序,以便分出一個(gè)優(yōu)先級出來,而且這個(gè)優(yōu)先級還不能相同。
所以接下來的 for 循環(huán)中就是判斷這個(gè)優(yōu)先級是否有相同的,要是有,直接拋出異常。
最后,遍歷 webSecurityConfigurers,并將其數(shù)據(jù)挨個(gè)配置到 webSecurity 中。webSecurity.apply 方法會將這些配置存入 AbstractConfiguredSecurityBuilder.configurers 屬性中(參見:深入理解 HttpSecurity【源碼篇】)。
這就是 setFilterChainProxySecurityConfigurer 方法的工作邏輯,大家看到,它主要是在構(gòu)造 WebSecurity 對象。
WebSecurityConfiguration 中第二個(gè)比較關(guān)鍵的方法是 springSecurityFilterChain,該方法是在上個(gè)方法執(zhí)行之后執(zhí)行,方法的目的是構(gòu)建過濾器鏈,我們來看下:
@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 存在,一般來說都是有的,即使你沒有配置,還有一個(gè)默認(rèn)的。當(dāng)然,如果不存在的話,這里會現(xiàn)場 new 一個(gè)出來,然后調(diào)用 apply 方法。
最最關(guān)鍵的就是最后的 webSecurity.build() 方法了,這個(gè)方法的調(diào)用就是去構(gòu)建過濾器鏈了。
根據(jù) 深入理解 HttpSecurity【源碼篇】 一文的介紹,這個(gè) build 方法最終是在 AbstractConfiguredSecurityBuilder#doBuild 方法中執(zhí)行的:
@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;
}
}
這里會記錄下來整個(gè)項(xiàng)目的構(gòu)建狀態(tài)。三個(gè)比較關(guān)鍵的方法,一個(gè)是 init、一個(gè) configure 還有一個(gè) performBuild。
init 方法會遍歷所有的 WebSecurityConfigurerAdapter ,并執(zhí)行其 init 方法。WebSecurityConfigurerAdapter#init 方法主要是做 HttpSecurity 的初始化工作,具體參考:深入理解 WebSecurityConfigurerAdapter【源碼篇】。init 方法在執(zhí)行時(shí),會涉及到 HttpSecurity 的初始化,而 HttpSecurity 的初始化,需要配置 AuthenticationManager,所以這里最終還會涉及到一些全局的 AuthenticationManagerBuilder 及相關(guān)屬性的初始化,具體參見:深入理解 AuthenticationManagerBuilder 【源碼篇】,需要注意的是,AuthenticationManager 在初始化的過程中,也會來到這個(gè) doBuild 方法中,具體參考松哥前面文章,這里就不再贅述。
configure 方法會遍歷所有的 WebSecurityConfigurerAdapter ,并執(zhí)行其 configure 方法。WebSecurityConfigurerAdapter#configure 方法默認(rèn)是一個(gè)空方法,開發(fā)者可以自己重寫該方法去定義自己的 WebSecurity,具體參考:深入理解 WebSecurityConfigurerAdapter【源碼篇】。
最后調(diào)用 performBuild 方法進(jìn)行構(gòu)建,這個(gè)最終執(zhí)行的是 WebSecurity#performBuild 方法,該方法執(zhí)行流程,參考松哥前面文章深入理解 WebSecurityConfigurerAdapter【源碼篇】。
performBuild 方法執(zhí)行的過程,也是過濾器鏈構(gòu)建的過程。里邊會調(diào)用到過濾器鏈的構(gòu)建方法,也就是默認(rèn)的十多個(gè)過濾器會挨個(gè)構(gòu)建,這個(gè)構(gòu)建過程也會調(diào)用到這個(gè) doBuild 方法。
performBuild 方法中將成功構(gòu)建 FilterChainProxy,最終形成我們需要的過濾器鏈。
@EnableWebSecurity 注解除了過濾器鏈的構(gòu)建,還有一個(gè)注解就是 @EnableGlobalAuthentication。我們也順便來看下:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
可以看到,該注解的作用主要是導(dǎo)入 AuthenticationConfiguration 配置。
這便是 Spring Security 的一個(gè)大致的初始化流程。大部分的源碼在前面的文章中都講過了,本文主要是是一個(gè)梳理,如果小伙伴們還沒看前面的文章,建議看過了再來學(xué)習(xí)本文哦。
看完上述內(nèi)容,你們掌握怎樣進(jìn)行Spring Security初始化流程梳理的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。