日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Spring Security之配置體系

作者:Evan_L 更新時間: 2024-07-18 編程語言

前言

本文跟大家聊聊Spring Security,看似簡單易用的配置,實際上很難理解的配置體系。

配置體系概要

Spring Security的配置體系在我看來,主要分為兩大類:

  • 負責構建核心組件的SecurityBuilder<O>
  • 負責對核心組件的Builder進行配置的SecurityConfigurer<O, B>

核心組件

先大致介紹一下Spring Security的核心組件,如此我們才能更好的理解相關配置。

  • FilterChainProxy
    這是個Filter。實際上是SpringSecurity的全權代理。從名字我們也能看出,他是一個FilterChain的“代理”。這里的“代理”類似于明星的經紀人,就是負責接活的人。而FilterChain是干活的。FilterChain代表的是一個基于SpringSecurity的安全過濾器鏈條。很明顯,他是一系列Filter的組合。

    在請求到來時,FilterChainProxy先遍歷所有的SecurityFilterChain,然后找到匹配的SecurityFilterChain,遍歷其中的Filter并調用doFilter方法,從而實現安全過濾器的邏輯。

  • DefaultSecurityFilterChain
    這是FilterChainProxy的助手,同時也是一個完整的安全過濾器鏈條。他為FilterChainProxy提供了滿足某一路徑條件所有Filter。

這兩級結構,為下面這一場景提供了可靠的安全解決方案:
應用中存在多個DispatcherServlet,分別負責不同的請求路徑。然后二者負責的功能,所要求的安全性不同。例如:一個為手機端應用提供服務,面向廣大市民,認證機制使用的是JWT。另一個負責的是后臺管理服務,認證機制使用的是Session,面向企業管理人員。
按照兩級結構,就基于兩個DispatcherServlet的路徑配置不同的SecurityFilterChain,交給FilterChainProxy即可。

核心組件的構造器

Spring Security抽象出一個共同的接口,兩個不同的構造器來分配構造上面兩個核心組件。

  • 構造器接口:
public interface SecurityBuilder<O> {
	O build() throws Exception;
}

這個也就是開頭提到構造體系的核心組件之一。

  • 兩個核心組件的建造者
    1. FilterChainProxy的構造器:WebSecurity
      這個相對簡單一些,不準備細聊,感興趣的可以自己看看源碼。
    2. DefaultSecurityFilterChain的構造器:HttpSecurity
      這個是我們主要配置的重點,也是復雜度爆表的。我們將重點聊這個。

HttpSecurity

為了搞清楚,我們需要先從HttpSecurity的“族譜及社會關系”開始。
HttpSecurity的“族譜及社會關系”
UML中的O,是DefaultSecurityFilterChain。
B和H都指代的則是構建者,也就是HttpSecurity 。B的作用之一就是方便你鏈式調用。

PS: 為了不至于上來就勸退,決定由淺入深的啃圖里的類/接口。

AbstractSecurityBuilder

/**
 * 基本的SecurityBuilder,主要是確保對象只構建一次。
 */
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
    @Override
 	public final O build() throws Exception {
 		// 利用AtomicBoolean,確保只構建一次
 		if (this.building.compareAndSet(false, true)) {
 			this.object = doBuild();
 			return this.object;
 		}
 		throw new AlreadyBuiltException("This object has already been built");
 	}
 	// 真正的構建方法,由繼承者實現。
 	protected abstract O doBuild() throws Exception;
 }

HttpSecurityBuilder

Http請求的安全構建者。這是個接口,定義的是Http請求的安全構建者的行為。雖然目前只有一個實現類HttpSecurity。但是大家一定要注意,從設計上的職責和定義上進行區分。

public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
	extends SecurityBuilder<DefaultSecurityFilterChain> {
	// 管理configurerer:2個方法
	<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);
	<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);
	// 管理sharedObjects:2個方法
	<C> void setSharedObject(Class<C> sharedType, C object);
	<C> C getSharedObject(Class<C> sharedType);
	// 這兩個方法與登錄有關,也是web安全的一部分。
	H authenticationProvider(AuthenticationProvider authenticationProvider);
	H userDetailsService(UserDetailsService userDetailsService) throws Exception;
	// 添加過濾器相關
	H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
	H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);
	H addFilter(Filter filter);
}

在職責上,繼承了SecurityBuilder,是DefaultSecurityFilterChain的構建者。
在定義上,引入了另一個泛型:H extends HttpSecurityBuilder<H>。第一眼是不是感覺云里霧里,不明就里?其實,我個人認為,這個HttpSecurityBuilder名字并不是很恰當的。我覺得ConfigurableSecurityBuilder或許更恰當。因為他定義的行為包括三部分:管理SecurityConfigurer、管理共享對象、過濾器相關。而以上行為的本質還是告訴建造者,如何構建DefaultSecurityFilterChain,可以理解為建造者的配置。
而H的作用是,限定行為/接口方法中的SecurityConfigurer的。而SecurityConfigurer本身又帶2個泛型,等會兒我們再細聊。

SecurityConfigurer

主要用于配置SecurityBuilder,幫助SecurityBuilder構建O。

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
	void init(B builder) throws Exception;
	void configure(B builder) throws Exception;
}

O是建造者B要建造的目標對象。這個類定義的意思是,我可以配置負責構造O的Builder。

init方法,可以初始化自身,也可以初始化builder(例如,提前設置一些屬性)。

configurer方法,就是配置builder的重頭戲了。一般來說,都是往builder里添加過濾器。

SecurityConfigurer有一個抽象類指定配置生產DefaultSecurityFilterChain的HttpSecurityBuilder。

public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
		extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {}

對應到SecurityConfigurer的泛型:
O為DefaultSecurityFilterChain, H為B。

B限定于生產DefaultSecurityFilterChain的HttpSecurityBuilder。額,還是覺得挺亂的是吧?從這里開始,復雜度就開始飆升了。系好安全帶,開車!

先用一句話概括:AbstractHttpConfigurer的職責是配置生產DefaultSecurityFilterChain的HttpSecurityBuilder。按照這個定義,我們簡化一下類定義,刪除掉泛型H,改成下面:

public abstract class AbstractHttpConfigurer<B extends HttpSecurityBuilder>
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B>

雖然定義還是很長,但已經能理解了。此時只有一個泛型B了,而泛型B的上界是HttpSecurityBuilder。
那么原來的T到底是要干嘛呢?從定義看:T extends AbstractHttpConfigurer<T, B>;無非就是指待某個具體的AbstractHttpConfigurer唄。因此,個人認為,只有一個作用,為了方便方法調用:方便返回值或入參的類型自動轉換。

AbstractConfiguredSecurityBuilder

通過SecurityConfigurer進行配置的SecurityBuilder的抽象類。

 public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
   extends AbstractSecurityBuilder<O> {
   // 記錄每一個SecurityConfigurer的Class對應的所有SecurityConfigurer注冊實例。
   private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
   // 共享對象。這里涉及到需要多個過濾器協作的對象。
   private final Map<Class<?>, Object> sharedObjects = new HashMap<>();
   // 為了支持對象的初始化。
   private ObjectPostProcessor<Object> objectPostProcessor;
   private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
   	// 為了節省篇幅,簡單說一下功能:登記/注冊configurer到this.configurers
   }

   /**
   * 核心構建方法:模板模式
    */
   @Override
   protected final O doBuild() throws Exception {
       // 同步鎖
   	synchronized (this.configurers) {
   		// 初始化階段
   		this.buildState = BuildState.INITIALIZING;
   		// 初始化前:允許子類進行初始化前的預處理。-鉤子方法
   		beforeInit();
   		// 初始化:調用所有的configurers的init方法
   		init();
   		// 配置階段
   		this.buildState = BuildState.CONFIGURING;
   		// 配置前:允許子類進行配置前的預處理。-鉤子方法
   		beforeConfigure();
   		// 進行配置:調用所有的configurers的configure方法
   		configure();
   		// 構建階段
   		this.buildState = BuildState.BUILDING;
   		// 執行構建;與其他方法不同,這是個抽象方法。因為只有具體實現類才知道要new什么對象。
   		O result = performBuild();
   		// 構建完成
   		this.buildState = BuildState.BUILT;
   		return result;
   	}
   }
}

不知道你們有沒有發現,這個類的定義與前面的SecurityConfigurer在泛型上,是一模一樣的!這樣的安排都是為了使用SecurityConfigurer。

  • 小結一下:
    作用一:支持通過SecurityConfigurer對構建者進行配置。
    作用二:支持對構建對象的IOC初始化過程,例如:afterPropertiesSet()、各種Aware的相關方法的調用。當然也可以通過注冊自定義的ObjectPostProcessor,對目標對象進行修改。
    作用三:通過模板模式,統一對象O的構建步驟/流程。基于SecurityConfigurer進行配置的構建流程。

Customizer<T>

沒想到吧?還有一個編外人員。別緊張,他是我們用來修改SecurityConfigurer的。上一篇文章的demo,就是一個Customizer。他的原理/思路也十分簡單,就是我把當前對象丟給你,你想配置什么,自己設置就是了。例如:

httpSecurity.csrf(csrfConfigurer -> csrfConfigurer.ignoringRequestMatchers(“/**/*.do”))`。

這意思就是,我給你傳一個配置HttpSecurity的CsrfConfigurer,HttpSecurity會調用Customizer#customize方法,然后就能執行到你配置的csrfConfigurer.ignoringRequestMatchers("/**/*.do"),對路徑/**/*.do不校驗csrfToken。
可能有同學不熟悉lambda表達式。上面csrf方法傳入的參數等價于

// CsrfConfigurer<HttpSecurity>是配置HttpSecurity的Configurer,他會引入CsrfFilter。
new Customizer<CsrfConfigurer<HttpSecurity>>() {
	public void customize(CsrfConfigurer csrfConfigurer) {
		csrfConfigurer.ignoringRequestMatchers("/**/*.do");
	}
}

HttpSecurity的詳解

好了,終于到這位大爺了。

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
	private final RequestMatcherConfigurer requestMatcherConfigurer;
	private List<OrderedFilter> filters = new ArrayList<>();
	// 
	private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
	private AuthenticationManager authenticationManager;
	
	public HttpSecurity csrf(Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer) throws Exception {
		ApplicationContext context = getContext();
		// 這里調用了3個方法,依次為:
		// 1. 創建CsrfConfigurer
		// 2. 類似于CAS,如果存在,就直接返回,否則就注冊。
		// 3. 調用Customizer.customize方法配置CsrfConfigurer
		csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context)));
		return HttpSecurity.this;
	}
	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
			throws Exception {
		// 從已注冊的configurers中獲取對應的configurer
		// 之所以會這樣設計,是因為需要對HttpSecurity進行一些默認配置。后面會說。
		// 因此需要確保配置的是同一個對象。
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}
	public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
		// 設置configurer的objectPostProcessor。后面configurer在配置時,會通過他對某些對象進行初始化。如此才能完成ObjectPostProcessor的初始化工作。
		configurer.addObjectPostProcessor(this.objectPostProcessor);
		// 構建者,為了便于configurer獲取共享對象。
		configurer.setBuilder((B) this);
		// 調用父類的add方法注冊configurer
		add(configurer);
		return configurer;
	}
	@Override
	protected DefaultSecurityFilterChain performBuild() {
		// 對過濾器進行排序
		this.filters.sort(OrderComparator.INSTANCE);
		List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
		for (Filter filter : this.filters) {
			sortedFilters.add(((OrderedFilter) filter).filter);
		}
		// 構建DefaultSecurityFilterChain
		return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
	}
}

由于可以配置的SecurityConfigurer實在是太多了,這里挑了相對簡單的CsrfConfigurer作為典型案例。具體的過程,如摘抄的源碼上的注釋,就不多啰嗦了。

通過SecurityConfigurer引入Filter

不知道大家有沒有這樣一個疑問:我們并沒有看到CsrfFilter是如何添加到HttpSecurity的filters屬性,只看到引入的CsrfConfigurer。而這就是,我們SecurityConfigurer最為重要的存在意義:引入Filter。
AbstractConfiguredSecurityBuilder#doBuild方法,我們知道在真正構建目標對象之前,會先后調用SecurityConfigurer#initSecurityConfigurer#configure方法。而引入Filter的密碼就藏在configure方法中。

public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
		
	@Override
	public void configure(H http) {
		// 創建CsrfFilter
		CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
		RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
		if (requireCsrfProtectionMatcher != null) {
			// 指定需要防御CSRF的請求
			filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
		}
		AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
		if (accessDeniedHandler != null) {
			// 指定訪問異常處理器
			filter.setAccessDeniedHandler(accessDeniedHandler);
		}
		LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
		if (logoutConfigurer != null) {
			// 登出接口也進行CSRF防御
			logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
		}
		SessionManagementConfigurer<H> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
		if (sessionConfigurer != null) {
			// 這里涉及Session管理,后面聊認證的時候再細聊
			sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());
		}
		// 通過ObjectPostProcessor完成初始化,或者修改
		filter = postProcess(filter);
		// 向HttpSecurity中添加CsrfFilter
		http.addFilter(filter);
	}
}

如源碼所示,其configure方法的核心業務流程為;

  1. 創建Filter
  2. 根據相關配置設置Filter
  3. 通過ObjectPostProcessor完成初始化
  4. 向HttpSecurity中添加Filter

幾乎所有與Filter相關的SecurityConfigurer都是這樣對HttpSecurity實現配置的。而配置的重點也就是向HttpSecurity中添加Filter。

HttpSecurity默認配置

新版本的默認配置在HttpSecurityConfiguration

class HttpSecurityConfiguration {
	@Bean(HTTPSECURITY_BEAN_NAME)
	@Scope("prototype")
	HttpSecurity httpSecurity() throws Exception {
		LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
		// 認證管理器的建造者,會放到
		AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
				this.objectPostProcessor, passwordEncoder);
		authenticationBuilder.parentAuthenticationManager(authenticationManager());
		authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
		// 創建HttpSecurity,同時會將authenticationBuilder放入sharedObjects中
		HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
		// 異步請求管理器,需要支持獲取當前請求的用戶憑證相關信息
		WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
		webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
		// @formatter:off
		http
			// 引入CsrfFilter
			.csrf(withDefaults())
			.addFilter(webAsyncManagerIntegrationFilter)
			// 引入異常處理器,以便處理權限異常
			.exceptionHandling(withDefaults())
			// 引入HeaderWriterFilter,便于設置默認的安全請求頭
			.headers(withDefaults())
			// 引入SessionManagementFilter,以便確保session相關功能
			.sessionManagement(withDefaults())
			// 引入SecurityContextHolderFilter,以便獲取當前用戶
			.securityContext(withDefaults())
			// 引入RequestCacheAwareFilter,以便恢復認證前的訪問異常請求
			.requestCache(withDefaults())
			// 引入AnonymousAuthenticationFilter,管理匿名用戶
			.anonymous(withDefaults())
			// 引入SecurityContextHolderAwareRequestFilter,以便基于servletApi實現認證
			.servletApi(withDefaults())
			// 引入DefaultLoginPageGeneratingFilter,以便自動生成登錄頁面
			.apply(new DefaultLoginPageConfigurer<>());
		// 引入LogoutFilter,以便處理默認的登出請求
		http.logout(withDefaults());
		// @formatter:on
		// 引入基于spring的spi機制引入的configurer,如果開發者沒有配置的,目前應該是沒有相關的configurer。
		applyDefaultConfigurers(http);
		return http;
	}
}

總結

  1. 配置體系主要包括:SecurityBuilder<O>SecurityConfigurer<O, B>

  2. FilterChainProxy由WebSecurity構建。

  3. DefaultSecurityFilterChain由HttpSecurity構建。

  4. SecurityConfigurer<O, B>負責配置SecurityBuilder,指引SecurityBuilder如何構建。

  5. HttpSecurity本質上是通過一系列的SecurityConfigurer<O, B>創建Filter,來構建DefaultSecurityFilterChain。

  6. 我們配置HttpSecurity的原理如下:

    HttpSecurity -> SecurityConfigurer<O, B> -> Customizer

    本質上是通過Customizer修改SecurityConfigurer,從而改變生成的Filter的屬性。

  7. 推薦使用基于Customizer的HttpSecurity方法進行配置。作用清晰:引入某一過濾器,配置明確:寫在Customizer里面了。

后記

本文我們討論了Spring Security的配置體系,希望大家看源碼時不至于在懵逼,琢磨某個Filter時不至于迷失在源碼里。下一次,我們聊聊UsernamePasswordAuthenticationFilter。相信大家期盼已久了吧。

參照

Spring Security的架構

原文鏈接:https://blog.csdn.net/Evan_L/article/details/136707082

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新