SpringSecurity啟動流程源碼解析 | 博客園新人第三彈


別辜負生命,別辜負自己。

楔子

前面兩期我講了SpringSecurity認證流程SpringSecurity鑒權流程,今天是第三期,是SpringSecurity的收尾工作,講SpringSecurity的啟動流程。

就像很多電影拍火了之后其續作往往是前作的前期故事一樣,我這個第三期要講的SpringSecurity啟動流程也是不擇不扣的"前期故事",它能幫助你真正認清SpringSecurity的整體全貌。

在之前的文章里,在說到SpringSecurity中的過濾器鏈的時候,往往是把它作為一個概念了解的,就是我們只是知道有這么個東西,也知道它到底是干什么用的,但是我們卻不知道這個過濾器鏈是由什么類什么時候去怎么樣創建出來的。

今天這期就是要了解SpringSecurity的自動配置到底幫我們做了什么,它是如何把過濾器鏈給創建出來的,又是在默認配置的時候怎么加入了我們的自定義配置。

祝有好收獲(邊贊邊看,法力無限)。

1. 📚EnableWebSecurity

我們先來看看我們一般是如何使用SpringSecurity的。

我們用SpringSecurity的時候都會先新建一個SpringSecurity相關的配置類,用它繼承WebSecurityConfigurerAdapter,然后打上注解@EnableWebSecurity,然后我們就可以通過重寫 WebSecurityConfigurerAdapter里面的方法來完成我們自己的自定義配置。

就像這樣:

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {   @Override  protected void configure(HttpSecurity http) throws Exception {   }  } 

我們已經知道,繼承WebSecurityConfigurerAdapter是為了重寫配置,那這個注解是做了什么呢?

從它的名字@EnableWebSecurity我們可以大概猜出來,它就是那個幫我們自動配置了SpringSecurity好心人

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class,  SpringWebMvcImportSelector.class,  OAuth2ImportSelector.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; } 

emmm,我猜大家應該有注解相關的知識吧,ok,既然你們都有注解相關的知識,我就直接講了。

這個@EnableWebSecurity中有兩個地方是比較重要的:

  • 一是@Import注解導入了三個類,這三個類中的后兩個是SpringSecurity為了兼容性做的一些東西,兼容SpringMVC,兼容SpringSecurityOAuth2,我們主要看的其實是第一個類,導入這個類代表了加載了這個類里面的內容。

  • 二是@EnableGlobalAuthentication這個注解,@EnableWebSecurity大家還沒搞明白呢,您這又來一個,這個注解呢,其作用也是加載了一個配置類-AuthenticationConfiguration,看它的名字大家也可應該知道它加載的類是什么相關的了吧,沒錯就是AuthenticationManager相關的配置類,這個我們可以以后再說。

綜上所述,@EnableWebSecurity可以說是幫我們自動加載了兩個配置類:WebSecurityConfigurationAuthenticationConfiguration(@EnableGlobalAuthentication注解加載了這個配置類)。

其中WebSecurityConfiguration是幫助我們建立了過濾器鏈的配置類,而AuthenticationConfiguration則是為我們注入AuthenticationManager相關的配置類,我們今天主要講的是WebSecurityConfiguration

2. 📖源碼概覽

既然講的是WebSecurityConfiguration,我們照例先把源碼給大家看看,精簡了一下無關緊要的:

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {  private WebSecurity webSecurity;   private Boolean debugEnabled;   private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;   private ClassLoader beanClassLoader;   @Autowired(required = false)  private ObjectPostProcessor<Object> objectObjectPostProcessor;    @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();  }    @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;  }  } 

如代碼所示,首先WebSecurityConfiguration是個配置類,類上面打了@Configuration注解,這個注解的作用大家還知道吧,在這里就是把這個類中所有帶@Bean注解的Bean給實例化一下。

這個類里面比較重要的就兩個方法:springSecurityFilterChainsetFilterChainProxySecurityConfigurer

springSecurityFilterChain方法上打了@Bean注解,任誰也能看出來就是這個方法創建了springSecurityFilterChain,但是先別着急,我們不能先看這個方法,雖然它在上面。

3. 📄SetFilterChainProxySecurityConfigurer

我們要先看下面的這個方法:setFilterChainProxySecurityConfigurer,為啥呢?

為啥呢?

因為它是@Autowired注解,所以它要比springSecurityFilterChain方法優先執行,從系統加載的順序來看,我們需要先看它。

@Autowired在這里的作用是為這個方法自動注入所需要的兩個參數,我們先來看看這兩個參數:

  • 參數objectPostProcessor是為了創建WebSecurity實例而注入進來的,先了解一下即可。

  • 參數webSecurityConfigurers是一個List,它實際上是所有WebSecurityConfigurerAdapter的子類,那如果我們定義了自定義的配置類,其實就是把我們的配置也讀取到了。

    這里其實有點難懂為什么參數中SecurityConfigurer<Filter, WebSecurity>這個類型可以拿到WebSecurityConfigurerAdapter的子類?

    因為WebSecurityConfigurerAdapter實現了WebSecurityConfigurer<WebSecurity>接口,而WebSecurityConfigurer<WebSecurity>又繼承了SecurityConfigurer<Filter, T>,經過一層實現,一層繼承關系之后,WebSecurityConfigurerAdapter終於成為了SecurityConfigurer的子類。

    而參數中SecurityConfigurer<Filter, WebSecurity>中的兩個泛型參數其實是起到了一個過濾的作用,仔細查看我們的WebSecurityConfigurerAdapter的實現與繼承關系,你可以發現我們的WebSecurityConfigurerAdapter正好是這種類型。

ok,說完了參數,我覺得我們可以看看代碼了:

@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(  ObjectPostProcessor<Object> objectPostProcessor,  @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)  throws Exception {   // 創建一個webSecurity實例  webSecurity = objectPostProcessor  .postProcess(new WebSecurity(objectPostProcessor));  if (debugEnabled != null) {  webSecurity.debug(debugEnabled);  }   // 根據order排序  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; } 

根據我們的注釋,這段代碼做的事情可以分為以為幾步:

  1. 創建了一個webSecurity實例,並且賦值給成員變量。

  2. 緊接着對webSecurityConfigurers通過order進行排序,order是加載順序。

  3. 進行判斷是否有相同order的配置類,如果出現將會直接報錯。

  4. 保存配置,將其放入webSecurity的成員變量中。

大家可以將這些直接理解為成員變量的初始化,和加載我們的配置類配置即可,因為后面的操作都是圍繞它初始化的webSecurity實例和我們加載的配置類信息來做的。

這些東西還可以拆出來一步步的來講,但是這樣的話真是一篇文章寫不完,我也沒有那么大的精力能夠事無巨細的寫出來,我只挑選這條痕跡清晰的主脈絡來講,如果大家看完能明白它的一個加載順序其實就挺好了。

就像Spring的面試題會問SpringBean的加載順序,SpringMVC則會問SpringMVC一個請求的運行過程一樣。

全部弄得明明白白,必須要精研源碼,在初期,我們只要知道它的一條主脈絡,在之后的使用中,哪出了問題你可以直接去定位到可能是哪有問題,這樣就已經很好了,學習是一個循環漸進的過程。

4. 📃SpringSecurityFilterChain

初始化完變量,加載完配置,我們要開始創建過濾器鏈了,所以先走setFilterChainProxySecurityConfigurer是有原因的,如果我們不把我們的自定義配置加載進來,創建過濾器鏈的時候怎么知道哪些過濾器需要哪些過濾器不需要。

@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();  } 

springSecurityFilterChain方法邏輯就很簡單了,如果我們沒加載自定義的配置類,它就替我們加載一個默認的配置類,然后調用這個build方法。

看到這熟悉的方法名稱,你就應該知道這是建造者模式,不管它什么模式,既然調用了,我們點進去就是了。

public final O build() throws Exception {
 if (this.building.compareAndSet(false, true)) {  this.object = doBuild();  return this.object;  }  throw new AlreadyBuiltException("This object has already been built");  } 

build()方法是webSecurity的父類AbstractSecurityBuilder中的方法,這個方法又調用了doBuild()方法。

@Override
protected final O doBuild() throws Exception {  synchronized (configurers) {  buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;   // 空方法  beforeInit();  // 調用init方法  init();   buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;   // 空方法  beforeConfigure();  // 調用configure方法  configure();   buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;   // 調用performBuild  O result = performBuild();   buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;   return result;  } } 

通過我的注釋可以看到beforeInit()beforeConfigure()都是空方法, 實際有用的只有init()configure()performBuild()方法。

我們先來看看init()configure()方法。

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);  }  } 

源碼中可以看到都是先獲取到我們的配置類信息,然后循環調用配置類自己的init()configure()方法。

前面說過,我們的配置類是繼承了WebSecurityConfigurerAdapter的子類,而WebSecurityConfigurerAdapter又是SecurityConfigurer的子類,所有SecurityConfigurer的子類都需要實現init()configure()方法。

所以這里的init()configure()方法其實就是調用WebSecurityConfigurerAdapter自己重寫的init()configure()方法。

其中WebSecurityConfigurerAdapter中的configure()方法是一個空方法,所以我們只需要去看WebSecurityConfigurerAdapter中的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);  });  } 

這里也可以分為兩步:

  1. 執行了getHttp()方法,這里面初始化加入了很多過濾器。

  2. HttpSecurity放入WebSecurity,將FilterSecurityInterceptor放入WebSecurity,就是我們鑒權那章講過的FilterSecurityInterceptor

那我們主要看第一步getHttp()方法:

protected final HttpSecurity getHttp() throws Exception {
 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;  } 

getHttp()方法里面http調用的那一堆方法都是一個個過濾器,第一個csrf()很明顯就是防止CSRF攻擊的過濾器,下面還有很多,這就是SpringSecurity默認會加入過濾器鏈的那些過濾器了。

其次,還有一個重點就是倒數第二行代碼,我也加上了注釋,我們一般在我們自定義的配置類中重寫的就是這個方法,所以我們的自定義配置就是在這里生效的。

所以在初始化的過程中,這個方法會先加載自己默認的配置然后再加載我們重寫的配置,這樣兩者結合起來,就變成了我們看到的默認配置。(如果我們不重寫configure(http)方法,它也會一點點的默認配置,大家可以去看源碼,看了就明白了。)

init()configure()(空方法)結束之后,就是調用performBuild()方法。

protected Filter performBuild() throws Exception {
  int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();   List<SecurityFilterChain> securityFilterChains = new ArrayList<>(  chainSize);   for (RequestMatcher ignoredRequest : ignoredRequests) {  securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));  }   // 調用securityFilterChainBuilder的build()方法  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;   postBuildAction.run();  return result;  } 

這個方法主要需要看的是調用securityFilterChainBuilderbuild()方法,這個securityFilterChainBuilder是我們在init()方法中add的那個,所以這里的securityFilterChainBuilder其實就是HttpSecurity,所以這里其實是調用了HttpSecuritybulid()方法。

又來了,WebSecuritybulid()方法還沒說完,先來了一下HttpSecuritybulid()方法。

HttpSecuritybulid()方法進程和之前的一樣,也是先init()然后configure()最后performBuild()方法,值得一提的是在HttpSecurityperformBuild()方法里面,會對過濾器鏈中的過濾器進行排序:

@Override
 protected DefaultSecurityFilterChain performBuild() {  filters.sort(comparator);  return new DefaultSecurityFilterChain(requestMatcher, filters);  } 

HttpSecuritybulid()方法執行完了之后將DefaultSecurityFilterChain返回給WebSecurityperformBuil()方法,performBuil()方法再將其轉換為FilterChainProxy,最后WebSecurityperformBuil()方法執行結束,返回一個Filter注入成為name="springSecurityFilterChain"Bean

經過以上這些步驟之后,springSecurityFilterChain方法執行完畢,我們的過濾器鏈就創建完成了,SpringSecurity也可以跑起來了。

后記

看到這的話,其實你已經很有耐性了,但可能還覺得雲里霧里的,因為SpringSecurity(Spring大家族)這種工程化極高的項目項目都是各種設計模式和編碼思想滿天飛,看不懂的時候只能說這什么玩意,看得懂的時候又該膜拜這是藝術啊。

這些東西它不容易看懂但是比較解耦容易擴展,像一條線下來的代碼就容易看懂但是不容易擴展了,福禍相依。

而且這么多名稱相近的類名,各種繼承抽象,要好好理解下來的確沒那么容易,這篇其實想給這個SpringSecurity來個收尾,逼着自己寫的,我這個人喜歡有始有終,這段東西也的確復雜,接下來的幾篇打算寫幾個實用的有意思的也輕松的放松一下。

如果你對SpringSecurity源碼有興趣可以跟着來我這個文章,點開你自己的源碼點一點,看一看,加油。

自從上篇征文發了之后,感覺多了很多前端的關注者,掘金果然還是前端多啊,沒事,雖然我不怎么寫前端,說不定哪天改行了呢哈哈。

我也不藏着掖着,其實我現在是寫后端的,我對前端呢只能說是略懂略懂,不過無聊了也可以來看看我的文章,點點贊刷刷閱讀干干啥的👍,說不定某一天突然看懂了某篇文還前端勸退后端入行,加油了大家。

別辜負生命,別辜負自己。

你們的每個點贊收藏與評論都是對我知識輸出的莫大肯定,如果有文中有什么錯誤或者疑點或者對我的指教都可以在評論區下方留言,一起討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM