我們繼續來擼 Spring Security 原始碼,今天來擼一個非常重要的 WebSecurityConfigurerAdapter。
我們的自定義都是繼承自 WebSecurityConfigurerAdapter 來實現的,但是對於 WebSecurityConfigurerAdapter 內部的工作原理,配置原理,很多小夥伴可能都還不太熟悉,因此我們今天就來捋一捋。
我們先來看一張 WebSecurityConfigurerAdapter 的繼承關係圖:
在這層繼承關係中,有兩個非常重要的類:
- SecurityBuilder
- SecurityConfigurer
這兩個類鬆哥在之前的文章中都和大家分享過了,具體參考:
- 深入理解 HttpSecurity【原始碼篇】(本文講的是 SecurityBuilder 體系)
- 深入理解 SecurityConfigurer 【原始碼篇】
所以關於這兩個類的介紹以及作用,鬆哥這裡就不贅述了。我們們直接從 WebSecurityConfigurer 開始看起。
1.WebSecurityConfigurer
WebSecurityConfigurer 其實是一個空介面,但是它裡邊約束了一些泛型,如下:
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
SecurityConfigurer<Filter, T> {
}
這裡邊的泛型很關鍵,這關乎到 WebSecurityConfigurer 的目的是啥!
- SecurityBuilder 中的泛型 Filter,表示 SecurityBuilder 最終的目的是為了構建一個 Filter 物件出來。
- SecurityConfigurer 中兩個泛型,第一個表示的含義也是 SecurityBuilder 最終構建的物件。
同時這裡還定義了新的泛型 T,T 需要繼承自 SecurityBuilder<Filter>,根據 WebSecurityConfigurerAdapter 中的定義,我們可以知道,T 就是 WebSecurity,我們也大概能猜出 WebSecurity 就是 SecurityBuilder<Filter> 的子類。
所以 WebSecurityConfigurer 的目的我們可以理解為就是為了配置 WebSecurity。
2.WebSecurity
我們來看下 WebSecurity 的定義:
public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
SecurityBuilder<Filter>, ApplicationContextAware {
}
沒錯,確實是這樣!WebSecurity 繼承自 AbstractConfiguredSecurityBuilder<Filter, WebSecurity> 同時實現了 SecurityBuilder<Filter> 介面。
WebSecurity 的這些介面和繼承類,鬆哥在前面的原始碼分析中都和大家介紹過了,可能有的小夥伴忘記了,我再來和大家複習一下。
AbstractConfiguredSecurityBuilder
首先 AbstractConfiguredSecurityBuilder 中定義了一個列舉類,將整個構建過程分為 5 種狀態,也可以理解為構建過程生命週期的五個階段,如下:
private enum BuildState {
UNBUILT(0),
INITIALIZING(1),
CONFIGURING(2),
BUILDING(3),
BUILT(4);
private final int order;
BuildState(int order) {
this.order = order;
}
public boolean isInitializing() {
return INITIALIZING.order == order;
}
public boolean isConfigured() {
return order >= CONFIGURING.order;
}
}
五種狀態分別是 UNBUILT、INITIALIZING、CONFIGURING、BUILDING 以及 BUILT。另外還提供了兩個判斷方法,isInitializing 判斷是否正在初始化,isConfigured 表示是否已經配置完畢。
AbstractConfiguredSecurityBuilder 中的方法比較多,鬆哥在這裡列出來兩個關鍵的方法和大家分析:
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized (configurers) {
if (buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer
+ " to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);
if (buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
List<SecurityConfigurer<O, B>> result = new ArrayList<>();
for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
result.addAll(configs);
}
return result;
}
第一個就是這個 add 方法,這相當於是在收集所有的配置類。將所有的 xxxConfigure 收集起來儲存到 configurers 中,將來再統一初始化並配置,configurers 本身是一個 LinkedHashMap ,key 是配置類的 class,value 是一個集合,集合裡邊放著 xxxConfigure 配置類。當需要對這些配置類進行集中配置的時候,會通過 getConfigurers 方法獲取配置類,這個獲取過程就是把 LinkedHashMap 中的 value 拿出來,放到一個集合中返回。
另一個方法就是 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;
}
}
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
在 AbstractSecurityBuilder 類中,過濾器的構建被轉移到 doBuild 方法上面了,不過在 AbstractSecurityBuilder 中只是定義了抽象的 doBuild 方法,具體的實現在 AbstractConfiguredSecurityBuilder。
doBuild 方法就是一邊更新狀態,進行進行初始化。
beforeInit 是一個預留方法,沒有任何實現。
init 方法就是找到所有的 xxxConfigure,挨個呼叫其 init 方法進行初始化。
beforeConfigure 是一個預留方法,沒有任何實現。
configure 方法就是找到所有的 xxxConfigure,挨個呼叫其 configure 方法進行配置。
最後則是 performBuild 方法,是真正的過濾器鏈構建方法,但是在 AbstractConfiguredSecurityBuilder 中 performBuild 方法只是一個抽象方法,具體的實現在它的子類中,也就是 WebSecurityConfigurer。
SecurityBuilder<Filter>
SecurityBuilder 就是用來構建過濾器鏈的,在 HttpSecurity 實現 SecurityBuilder 時,傳入的泛型就是 DefaultSecurityFilterChain,所以 SecurityBuilder#build 方法的功能很明確,就是用來構建一個過濾器鏈出來,但是那個過濾器鏈是 Spring Security 中的。在 WebSecurityConfigurerAdapter 中定義的泛型是 SecurityBuilder<Filter>,所以最終構建的是一個普通 Filter,其實就是 FilterChainProxy,關於 FilterChainProxy ,大家可以參考深入理解 FilterChainProxy【原始碼篇】。
WebSecurity
WebSecurity 的核心邏輯集中在 performBuild 構建方法上,我們一起來看下:
@Override
protected Filter performBuild() throws Exception {
Assert.state(
!securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
+ "More advanced users can invoke "
+ WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (debugEnabled) {
logger.warn("\n\n"
+ "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
postBuildAction.run();
return result;
}
先來說一句,這裡的 performBuild 方法只有一個功能,那就是構建 FilterChainProxy,如果你還不瞭解什麼是 FilterChainProxy,可以參考鬆哥之前的介紹:深入理解 FilterChainProxy【原始碼篇】。
把握住了這條主線,我們再來看方法的實現就很容易了。
- 首先統計過濾器鏈的總條數,總條數包括兩個方面,一個是 ignoredRequests,這是忽略的請求,通過 WebSecurity 配置的忽略請求,鬆哥之前介紹過,參見:Spring Security 兩種資源放行策略,千萬別用錯了!,另一個則是 securityFilterChainBuilders,也就是我們通過 HttpSecurity 配置的過濾器鏈,有幾個就算幾個。
- 建立 securityFilterChains 集合,並且遍歷上面提到的兩種型別的過濾器鏈,並將過濾器鏈放入 securityFilterChains 集合中。
- 我在深入理解 HttpSecurity【原始碼篇】一文中介紹過,HttpSecurity 構建出來的過濾器鏈物件就是 DefaultSecurityFilterChain,所以可以直接將 build 結果放入 securityFilterChains 中,而 ignoredRequests 中儲存的則需要重構一下才可以存入 securityFilterChains。
- securityFilterChains 中有資料之後,接下來建立一個 FilterChainProxy。
- 給新建的 FilterChainProxy 配置上防火牆,防火牆的介紹參考鬆哥之前的:Spring Security 自帶防火牆!你都不知道自己的系統有多安全!。
- 最後我們返回的就是 FilterChainProxy 的例項。
從這段分析中,我們可以看出來 WebSecurity 和 HttpSecurity 的區別:
- HttpSecurity 目的是構建過濾器鏈,一個 HttpSecurity 物件構建一條過濾器鏈,一個過濾器鏈中有 N 個過濾器,HttpSecurity 所做的事情實際上就是在配置這 N 個過濾器。
- WebSecurity 目的是構建 FilterChainProxy,一個 FilterChainProxy 中包含有多個過濾器鏈和一個 Firewall。
這就是 WebSecurity 的主要作用,核心方法是 performBuild,其他方法都比較簡單,鬆哥就不一一解釋了。
3.WebSecurityConfigurerAdapter
最後我們再來看 WebSecurityConfigurerAdapter,由於 WebSecurityConfigurer 只是一個空介面,WebSecurityConfigurerAdapter 就是針對這個空介面提供一個具體的實現,最終目的還是為了方便你配置 WebSecurity。
WebSecurityConfigurerAdapter 中的方法比較多,但是根據我們前面的分析,提綱挈領的方法就兩個,一個是 init,還有一個 configure(WebSecurity web),其他方法都是為這兩個方法服務的。那我們就來看下這兩個方法:
先看 init 方法:
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
});
}
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
Map<Class<?>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
configure(http);
return http;
}
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
init 方法可以算是這裡的入口方法了:首先呼叫 getHttp 方法進行 HttpSecurity 的初始化。HttpSecurity 的初始化,實際上就是配置了一堆預設的過濾器,配置完成後,最終還呼叫了 configure(http) 方法,該方法又配置了一些攔截器,不過在實際開發中,我們經常會重寫 configure(http) 方法,在鬆哥本系列前面的文章中,configure(http) 方法幾乎都有重寫。HttpSecurity 配置完成後,再將 HttpSecurity 放入 WebSecurity 中,儲存在 WebSecurity 的 securityFilterChainBuilders 集合裡,具體參見:深入理解 HttpSecurity【原始碼篇】。
configure(WebSecurity web) 方法實際上是一個空方法,我們在實際開發中可能會重寫該方法(參見 Spring Security 兩種資源放行策略,千萬別用錯了! 一文):
public void configure(WebSecurity web) throws Exception {
}
4.小結
這便是 WebSecurityConfigurerAdapter,整體上來說並不難,但是要和鬆哥前面幾篇原始碼分析文章一起看,理解會更加深刻一些。
傳送門:
- 深入理解 FilterChainProxy【原始碼篇】
- 深入理解 SecurityConfigurer 【原始碼篇】
- 深入理解 HttpSecurity【原始碼篇】
- 深入理解 AuthenticationManagerBuilder 【原始碼篇】
好啦,小夥伴們要是有收穫,記得點個在看鼓勵下鬆哥哦~