溫馨提示×

溫馨提示×

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

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

怎樣進行Spring Security初始化流程梳理

發布時間:2021-12-02 15:37:49 來源:億速云 閱讀:196 作者:柒染 欄目:大數據

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

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

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

 

1.SecurityAutoConfiguration

Spring Security 的自動化配置類是 SecurityAutoConfiguration,我們就從這個配置類開始分析。

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

}
 

這個 Bean 中,定義了一個事件發布器。另外導入了三個配置:

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

2.WebSecurityEnablerConfiguration

接著來看上面出現的 WebSecurityEnablerConfiguration:

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

}
 

這個配置倒沒啥可說的,給了一堆生效條件,最終給出了一個 @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. 導入 WebSecurityConfiguration 配置。
  2. 通過 @EnableGlobalAuthentication 注解引入全局配置。
 

3.1 WebSecurityConfiguration

WebSecurityConfiguration 類實現了兩個接口,我們來分別看下:

public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
}
 

ImportAware 接口和 @Import 注解一起使用的。實現了 ImportAware 接口的配置類可以方便的通過 setImportMetadata 方法獲取到導入類中的數據配置。

可能有點繞,我再梳理下,就是 WebSecurityConfiguration 實現了 ImportAware 接口,使用 @Import 注解在 @EnableWebSecurity 上導入 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。

實現 BeanClassLoaderAware 接口則是為了方便的獲取 ClassLoader。

這是 WebSecurityConfiguration 實現的兩個接口。

在 WebSecurityConfiguration 內部定義的 Bean 中,最為重要的是兩個方法:

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

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

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

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

在 WebSecurityConfiguration 中定義了該實例:

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

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

WebSecurityConfigurer 的實例其實就是 WebSecurityConfigurerAdapter,如果我們沒有自定義 WebSecurityConfigurerAdapter,那么默認使用的是 SpringBootWebSecurityConfiguration 中自定義的 WebSecurityConfigurerAdapter。

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

至此,我們搞明白了了 setFilterChainProxySecurityConfigurer 方法的兩個參數?;氐皆摲椒ㄎ覀兝^續分析。

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

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

所以接下來的 for 循環中就是判斷這個優先級是否有相同的,要是有,直接拋出異常。

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

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

WebSecurityConfiguration 中第二個比較關鍵的方法是 springSecurityFilterChain,該方法是在上個方法執行之后執行,方法的目的是構建過濾器鏈,我們來看下:

@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 存在,一般來說都是有的,即使你沒有配置,還有一個默認的。當然,如果不存在的話,這里會現場 new 一個出來,然后調用 apply 方法。

最最關鍵的就是最后的 webSecurity.build() 方法了,這個方法的調用就是去構建過濾器鏈了。

根據 深入理解 HttpSecurity【源碼篇】 一文的介紹,這個 build 方法最終是在 AbstractConfiguredSecurityBuilder#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;
 }
}
 

這里會記錄下來整個項目的構建狀態。三個比較關鍵的方法,一個是 init、一個 configure 還有一個 performBuild。

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

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

最后調用 performBuild 方法進行構建,這個最終執行的是 WebSecurity#performBuild 方法,該方法執行流程,參考松哥前面文章深入理解 WebSecurityConfigurerAdapter【源碼篇】。

performBuild 方法執行的過程,也是過濾器鏈構建的過程。里邊會調用到過濾器鏈的構建方法,也就是默認的十多個過濾器會挨個構建,這個構建過程也會調用到這個 doBuild 方法。

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

 

3.2 @EnableGlobalAuthentication

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

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

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

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

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

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女