深刻理解 WebSecurityConfigurerAdapter【源碼篇】

鬆哥原創的 Spring Boot 視頻教程已經殺青,感興趣的小夥伴戳這裏-->Spring Boot+Vue+微人事視頻教程java


咱們繼續來擼 Spring Security 源碼,今天來擼一個很是重要的 WebSecurityConfigurerAdapter。web

咱們的自定義都是繼承自 WebSecurityConfigurerAdapter 來實現的,可是對於 WebSecurityConfigurerAdapter 內部的工做原理,配置原理,不少小夥伴可能都還不太熟悉,所以咱們今天就來捋一捋。安全

咱們先來看一張 WebSecurityConfigurerAdapter 的繼承關係圖:微信

在這層繼承關係中,有兩個很是重要的類:session

  • SecurityBuilder
  • SecurityConfigurer

這兩個類鬆哥在以前的文章中都和你們分享過了,具體參考:app

因此關於這兩個類的介紹以及做用,鬆哥這裏就不贅述了。我們直接從 WebSecurityConfigurer 開始看起。編輯器

1.WebSecurityConfigurer

WebSecurityConfigurer 實際上是一個空接口,可是它裏邊約束了一些泛型,以下:ide

public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
  SecurityConfigurer<FilterT
{

}

這裏邊的泛型很關鍵,這關乎到 WebSecurityConfigurer 的目的是啥!源碼分析

  1. SecurityBuilder 中的泛型 Filter,表示 SecurityBuilder 最終的目的是爲了構建一個 Filter 對象出來。
  2. SecurityConfigurer 中兩個泛型,第一個表示的含義也是 SecurityBuilder 最終構建的對象。

同時這裏還定義了新的泛型 T,T 須要繼承自 SecurityBuilder ,根據 WebSecurityConfigurerAdapter 中的定義,咱們能夠知道,T 就是 WebSecurity,咱們也大概能猜出 WebSecurity 就是 SecurityBuilder 的子類。 post

因此 WebSecurityConfigurer 的目的咱們能夠理解爲就是爲了配置 WebSecurity。

2.WebSecurity

咱們來看下 WebSecurity 的定義:

public final class WebSecurity extends
  AbstractConfiguredSecurityBuilder<FilterWebSecurityimplements
  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 ,因此最終構建的是一個普通 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【源碼篇】

把握住了這條主線,咱們再來看方法的實現就很容易了。

  1. 首先統計過濾器鏈的總條數,總條數包括兩個方面,一個是 ignoredRequests,這是忽略的請求,經過 WebSecurity 配置的忽略請求,鬆哥以前介紹過,參見: Spring Security 兩種資源放行策略,千萬別用錯了!,另外一個則是 securityFilterChainBuilders,也就是咱們經過 HttpSecurity 配置的過濾器鏈,有幾個就算幾個。
  2. 建立 securityFilterChains 集合,而且遍歷上面提到的兩種類型的過濾器鏈,並將過濾器鏈放入 securityFilterChains 集合中。
  3. 我在 深刻理解 HttpSecurity【源碼篇】一文中介紹過,HttpSecurity 構建出來的過濾器鏈對象就是 DefaultSecurityFilterChain,因此能夠直接將 build 結果放入 securityFilterChains 中,而 ignoredRequests 中保存的則須要重構一下才能夠存入 securityFilterChains。
  4. securityFilterChains 中有數據以後,接下來建立一個 FilterChainProxy。
  5. 給新建的 FilterChainProxy 配置上防火牆,防火牆的介紹參考鬆哥以前的: Spring Security 自帶防火牆!你都不知道本身的系統有多安全!
  6. 最後咱們返回的就是 FilterChainProxy 的實例。

從這段分析中,咱們能夠看出來 WebSecurity 和 HttpSecurity 的區別:

  1. HttpSecurity 目的是構建過濾器鏈,一個 HttpSecurity 對象構建一條過濾器鏈,一個過濾器鏈中有 N 個過濾器,HttpSecurity 所作的事情實際上就是在配置這 N 個過濾器。
  2. 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,總體上來講並不難,可是要和鬆哥前面幾篇源碼分析文章一塊兒看,理解會更加深入一些。

傳送門:

  1. 深刻理解 FilterChainProxy【源碼篇】
  2. 深刻理解 SecurityConfigurer 【源碼篇】
  3. 深刻理解 HttpSecurity【源碼篇】
  4. 深刻理解 AuthenticationManagerBuilder 【源碼篇】

好啦,小夥伴們要是有收穫,記得點個在看鼓勵下鬆哥哦~

今日干貨

剛剛發表
查看: 13500 回覆:135

公衆號後臺回覆 SpringBoot,免費獲取 274 頁SpringBoot修煉手冊。

本文分享自微信公衆號 - 江南一點雨(a_javaboy)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。