您好,登錄后才能下訂單哦!
怎樣進行Spring Security初始化流程梳理,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
今天試著來和大家捋一遍 Spring Security 的初始化流程。
在 Spring Boot 中,Spring Security 的初始化,我們就從自動化配置開始分析吧!
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 中,定義了一個事件發布器。另外導入了三個配置:
接著來看上面出現的 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 注解身上了。
@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 類實現了兩個接口,我們來分別看下:
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 中,最為重要的是兩個方法:
這兩個方法是核心,我們來逐一分析,先來看 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,最終形成我們需要的過濾器鏈。
@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 配置。
這便是 Spring Security 的一個大致的初始化流程。大部分的源碼在前面的文章中都講過了,本文主要是是一個梳理,如果小伙伴們還沒看前面的文章,建議看過了再來學習本文哦。
看完上述內容,你們掌握怎樣進行Spring Security初始化流程梳理的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。