溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎樣進(jìn)行Spring Security初始化流程梳理

發(fā)布時(shí)間:2021-12-02 15:37:49 來源:億速云 閱讀:143 作者:柒染 欄目:大數(shù)據(jù)

怎樣進(jìn)行Spring Security初始化流程梳理,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。

今天試著來和大家捋一遍 Spring Security 的初始化流程。

在 Spring Boot 中,Spring Security 的初始化,我們就從自動(dòng)化配置開始分析吧!

 

1.SecurityAutoConfiguration

Spring Security 的自動(dòng)化配置類是 SecurityAutoConfiguration,我們就從這個(gè)配置類開始分析。

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

}
 

這個(gè) Bean 中,定義了一個(gè)事件發(fā)布器。另外導(dǎo)入了三個(gè)配置:

  1. SpringBootWebSecurityConfiguration:這個(gè)配置的作用是在如果開發(fā)者沒有自定義 WebSecurityConfigurerAdapter 的話,這里提供一個(gè)默認(rèn)的實(shí)現(xiàn)。
  2. WebSecurityEnablerConfiguration:這個(gè)配置是 Spring Security 的核心配置,也將是我們分析的重點(diǎn)。
  3. SecurityDataConfiguration:提供了 Spring Security 整合 Spring Data 的支持,由于國內(nèi)使用 MyBatis 較多,所以這個(gè)配置發(fā)光發(fā)熱的場景有限。
 

2.WebSecurityEnablerConfiguration

接著來看上面出現(xiàn)的 WebSecurityEnablerConfiguration:

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {

}
 

這個(gè)配置倒沒啥可說的,給了一堆生效條件,最終給出了一個(gè) @EnableWebSecurity 注解,看來初始化重任落在 @EnableWebSecurity 注解身上了。

 

3.@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 所做的事情,有兩件比較重要:

  1. 導(dǎo)入 WebSecurityConfiguration 配置。
  2. 通過 @EnableGlobalAuthentication 注解引入全局配置。
 

3.1 WebSecurityConfiguration

WebSecurityConfiguration 類實(shí)現(xiàn)了兩個(gè)接口,我們來分別看下:

public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
}
 

ImportAware 接口和 @Import 注解一起使用的。實(shí)現(xiàn)了 ImportAware 接口的配置類可以方便的通過 setImportMetadata 方法獲取到導(dǎo)入類中的數(shù)據(jù)配置。

可能有點(diǎn)繞,我再梳理下,就是 WebSecurityConfiguration 實(shí)現(xiàn)了 ImportAware 接口,使用 @Import 注解在 @EnableWebSecurity 上導(dǎo)入 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。

實(shí)現(xiàn) BeanClassLoaderAware 接口則是為了方便的獲取 ClassLoader。

這是 WebSecurityConfiguration 實(shí)現(xiàn)的兩個(gè)接口。

在 WebSecurityConfiguration 內(nèi)部定義的 Bean 中,最為重要的是兩個(gè)方法:

  1. springSecurityFilterChain 該方法目的是為了獲取過濾器鏈。
  2. setFilterChainProxySecurityConfigurer 這個(gè)方法是為了收集配置類并創(chuàng)建 WebSecurity。

這兩個(gè)方法是核心,我們來逐一分析,先來看 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;
}
 

首先這個(gè)方法有兩個(gè)參數(shù),兩個(gè)參數(shù)都會自動(dòng)進(jìn)行注入,第一個(gè)參數(shù) ObjectPostProcessor 是一個(gè)后置處理器,默認(rèn)的實(shí)現(xiàn)是 AutowireBeanFactoryObjectPostProcessor,主要是為了將 new 出來的對象注入到 Spring 容器中(參見深入理解 SecurityConfigurer 【源碼篇】)。

第二個(gè)參數(shù) webSecurityConfigurers 是一個(gè)集合,這個(gè)集合里存放的都是 SecurityConfigurer,我們前面分析的過濾器鏈中過濾器的配置器,包括 WebSecurityConfigurerAdapter 的子類,都是 SecurityConfigurer 的實(shí)現(xiàn)類。根據(jù) @Value 注解中的描述,我們可以知道,這個(gè)集合中的數(shù)據(jù)來自 autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers() 方法。

在 WebSecurityConfiguration 中定義了該實(shí)例:

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

可以看到,其實(shí)就是從 beanFactory 工廠中查詢到 WebSecurityConfigurer 的實(shí)例返回。

WebSecurityConfigurer 的實(shí)例其實(shí)就是 WebSecurityConfigurerAdapter,如果我們沒有自定義 WebSecurityConfigurerAdapter,那么默認(rèn)使用的是 SpringBootWebSecurityConfiguration 中自定義的 WebSecurityConfigurerAdapter。

當(dāng)然我們也可能自定義了 WebSecurityConfigurerAdapter,而且如果我們配置了多個(gè)過濾器鏈(多個(gè) HttpSecurity 配置),那么 WebSecurityConfigurerAdapter 的實(shí)例也將有多個(gè)。所以這里返回的是 List 集合。

至此,我們搞明白了了 setFilterChainProxySecurityConfigurer 方法的兩個(gè)參數(shù)。回到該方法我們繼續(xù)分析。

接下來創(chuàng)建了 webSecurity 對象,并且放到 ObjectPostProcessor 中處理了一下,也就是把 new 出來的對象存入 Spring 容器中。

調(diào)用 webSecurityConfigurers.sort 方法對 WebSecurityConfigurerAdapter 進(jìn)行排序,如果我們配置了多個(gè) WebSecurityConfigurerAdapter 實(shí)例(多個(gè)過濾器鏈,參見:Spring Security 竟然可以同時(shí)存在多個(gè)過濾器鏈?),那么我們肯定要通過 @Order 注解對其進(jìn)行排序,以便分出一個(gè)優(yōu)先級出來,而且這個(gè)優(yōu)先級還不能相同。

所以接下來的 for 循環(huán)中就是判斷這個(gè)優(yōu)先級是否有相同的,要是有,直接拋出異常。

最后,遍歷 webSecurityConfigurers,并將其數(shù)據(jù)挨個(gè)配置到 webSecurity 中。webSecurity.apply 方法會將這些配置存入 AbstractConfiguredSecurityBuilder.configurers 屬性中(參見:深入理解 HttpSecurity【源碼篇】)。

這就是 setFilterChainProxySecurityConfigurer 方法的工作邏輯,大家看到,它主要是在構(gòu)造 WebSecurity 對象。

WebSecurityConfiguration 中第二個(gè)比較關(guān)鍵的方法是 springSecurityFilterChain,該方法是在上個(gè)方法執(zhí)行之后執(zhí)行,方法的目的是構(gòu)建過濾器鏈,我們來看下:

@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 存在,一般來說都是有的,即使你沒有配置,還有一個(gè)默認(rèn)的。當(dāng)然,如果不存在的話,這里會現(xiàn)場 new 一個(gè)出來,然后調(diào)用 apply 方法。

最最關(guān)鍵的就是最后的 webSecurity.build() 方法了,這個(gè)方法的調(diào)用就是去構(gòu)建過濾器鏈了。

根據(jù) 深入理解 HttpSecurity【源碼篇】 一文的介紹,這個(gè) build 方法最終是在 AbstractConfiguredSecurityBuilder#doBuild 方法中執(zhí)行的:

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

這里會記錄下來整個(gè)項(xiàng)目的構(gòu)建狀態(tài)。三個(gè)比較關(guān)鍵的方法,一個(gè)是 init、一個(gè) configure 還有一個(gè) performBuild。

init 方法會遍歷所有的 WebSecurityConfigurerAdapter ,并執(zhí)行其 init 方法。WebSecurityConfigurerAdapter#init 方法主要是做 HttpSecurity 的初始化工作,具體參考:深入理解 WebSecurityConfigurerAdapter【源碼篇】。init 方法在執(zhí)行時(shí),會涉及到 HttpSecurity 的初始化,而 HttpSecurity 的初始化,需要配置 AuthenticationManager,所以這里最終還會涉及到一些全局的 AuthenticationManagerBuilder 及相關(guān)屬性的初始化,具體參見:深入理解 AuthenticationManagerBuilder 【源碼篇】,需要注意的是,AuthenticationManager 在初始化的過程中,也會來到這個(gè) doBuild 方法中,具體參考松哥前面文章,這里就不再贅述。

configure 方法會遍歷所有的 WebSecurityConfigurerAdapter ,并執(zhí)行其 configure 方法。WebSecurityConfigurerAdapter#configure 方法默認(rèn)是一個(gè)空方法,開發(fā)者可以自己重寫該方法去定義自己的 WebSecurity,具體參考:深入理解 WebSecurityConfigurerAdapter【源碼篇】。

最后調(diào)用 performBuild 方法進(jìn)行構(gòu)建,這個(gè)最終執(zhí)行的是 WebSecurity#performBuild 方法,該方法執(zhí)行流程,參考松哥前面文章深入理解 WebSecurityConfigurerAdapter【源碼篇】。

performBuild 方法執(zhí)行的過程,也是過濾器鏈構(gòu)建的過程。里邊會調(diào)用到過濾器鏈的構(gòu)建方法,也就是默認(rèn)的十多個(gè)過濾器會挨個(gè)構(gòu)建,這個(gè)構(gòu)建過程也會調(diào)用到這個(gè) doBuild 方法。

performBuild 方法中將成功構(gòu)建 FilterChainProxy,最終形成我們需要的過濾器鏈。

 

3.2 @EnableGlobalAuthentication

@EnableWebSecurity 注解除了過濾器鏈的構(gòu)建,還有一個(gè)注解就是 @EnableGlobalAuthentication。我們也順便來看下:

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
 

可以看到,該注解的作用主要是導(dǎo)入 AuthenticationConfiguration 配置。

這便是 Spring Security 的一個(gè)大致的初始化流程。大部分的源碼在前面的文章中都講過了,本文主要是是一個(gè)梳理,如果小伙伴們還沒看前面的文章,建議看過了再來學(xué)習(xí)本文哦。

看完上述內(nèi)容,你們掌握怎樣進(jìn)行Spring Security初始化流程梳理的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI