怎樣進行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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。