鬆哥原創的 Spring Boot 視頻教程已經殺青,感興趣的小夥伴戳這裏-->Spring Boot+Vue+微人事視頻教程java
咱們繼續來擼 Spring Security 源碼,今天來擼一個很是重要的 WebSecurityConfigurerAdapter。web
咱們的自定義都是繼承自 WebSecurityConfigurerAdapter 來實現的,可是對於 WebSecurityConfigurerAdapter 內部的工做原理,配置原理,不少小夥伴可能都還不太熟悉,所以咱們今天就來捋一捋。安全
咱們先來看一張 WebSecurityConfigurerAdapter 的繼承關係圖:微信
![](http://static.javashuo.com/static/loading.gif)
在這層繼承關係中,有兩個很是重要的類:session
-
SecurityBuilder -
SecurityConfigurer
這兩個類鬆哥在以前的文章中都和你們分享過了,具體參考:app
-
深刻理解 HttpSecurity【源碼篇】(本文講的是 SecurityBuilder 體系) -
深刻理解 SecurityConfigurer 【源碼篇】
因此關於這兩個類的介紹以及做用,鬆哥這裏就不贅述了。我們直接從 WebSecurityConfigurer 開始看起。編輯器
1.WebSecurityConfigurer
WebSecurityConfigurer 實際上是一個空接口,可是它裏邊約束了一些泛型,以下:ide
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
SecurityConfigurer<Filter, T> {
}
這裏邊的泛型很關鍵,這關乎到 WebSecurityConfigurer 的目的是啥!源碼分析
-
SecurityBuilder 中的泛型 Filter,表示 SecurityBuilder 最終的目的是爲了構建一個 Filter 對象出來。 -
SecurityConfigurer 中兩個泛型,第一個表示的含義也是 SecurityBuilder 最終構建的對象。
同時這裏還定義了新的泛型 T,T 須要繼承自 SecurityBuilder
因此 WebSecurityConfigurer 的目的咱們能夠理解爲就是爲了配置 WebSecurity。
2.WebSecurity
咱們來看下 WebSecurity 的定義:
public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
SecurityBuilder<Filter>, ApplicationContextAware {
}
沒錯,確實是這樣!WebSecurity 繼承自 AbstractConfiguredSecurityBuilder<Filter, WebSecurity> 同時實現了 SecurityBuilder
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
SecurityBuilder 就是用來構建過濾器鏈的,在 HttpSecurity 實現 SecurityBuilder 時,傳入的泛型就是 DefaultSecurityFilterChain,因此 SecurityBuilder#build 方法的功能很明確,就是用來構建一個過濾器鏈出來,可是那個過濾器鏈是 Spring Security 中的。在 WebSecurityConfigurerAdapter 中定義的泛型是 SecurityBuilder
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 【源碼篇】
好啦,小夥伴們要是有收穫,記得點個在看鼓勵下鬆哥哦~
今日干貨
公衆號後臺回覆 SpringBoot,免費獲取 274 頁SpringBoot修煉手冊。
本文分享自微信公衆號 - 江南一點雨(a_javaboy)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。