Spring Security非常難的地方就是這個了
Spring Security最難的地方就是HttpSecurity的頂層設計。不信你看看HttpSecurity的定義。
- public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
- implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
- // 省略
- }
感覺不到的話,再給你看看UML圖:
為什么要這么復雜?我第一次看到HttpSecurity的結構時我懷疑我自己是不是Java開發。多年以后,當我深入學習了之后才理解了這種設計。作為一個框架,尤其是安全框架,配置必須足夠靈活才能適用于更多的業務場景。Spring Security采取了配置與構建分離的架構設計來保證這一點。
配置與構建分離
配置只需要去收集配置項,構建只需要把所有的配置構建成目標對象。各干各的,分離職責,這種做法能夠提高代碼的可維護性和可讀寫性。Spring Security利用接口隔離把配置和構建進行高度抽象,提高靈活度,降低復雜度。不過這個體系依然非常龐大。為了降低學習難度需要把大問題拆解成小問題,各個擊破,這種學習方法在學習一些復雜的抽象理論時很湊效。
SecurityBuilder
SecurityBuilder就是對構建的抽象。你看上面的類圖過于復雜,而看SecurityBuilder就非常的簡單了。
- public interface SecurityBuilder<O> {
- // 構建
- O build() throws Exception;
- }
就一個動作,構建泛化的目標對象O。通過下面這一組抽象和具體的定義我想你應該明白SecurityBuilder了吧。
- // 抽象
- SecurityBuilder -> O
- // 具體
- HttpSecurity->DefaultSecurityFilterChain
一句話,構建的活都是我來干。
- public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
- private AtomicBoolean building = new AtomicBoolean();
- private O object;
- @Override
- 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");
- }
- // 獲取構建目標對象
- public final O getObject() {
- if (!this.building.get()) {
- throw new IllegalStateException("This object has not been built");
- }
- return this.object;
- }
- /**
- * 鉤子方法
- */
- protected abstract O doBuild() throws Exception;
- }
它通過原子類AtomicBoolean對構建方法build()進行了調用限制:每個目標對象只能被構建一次,避免安全策略發生不一致的情況。構建方法還加了final關鍵字,不可覆寫!構建的核心邏輯通過預留的鉤子方法doBuild()來擴展,鉤子方法是很常見的一種繼承策略。另外AbstractSecurityBuilder還提供了獲取已構建目標對象的方法getObject。
一句話,構建的活我只干一次。
HttpSecurityBuilder
- public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
- extends SecurityBuilder<DefaultSecurityFilterChain> {
- // 根據類名獲取配置
- <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);
- // 根據類名移除配置
- <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);
- // 把某個對象設置為共享,以便于在多個SecurityConfigurer中使用
- <C> void setSharedObject(Class<C> sharedType, C object);
- // 獲取某個共享對象
- <C> C getSharedObject(Class<C> sharedType);
- // 添加額外的 AuthenticationProvider
- H authenticationProvider(AuthenticationProvider authenticationProvider);
- // 添加額外的 UserDetailsService
- H userDetailsService(UserDetailsService userDetailsService) throws Exception;
- // 在過濾器鏈已有的afterFilter類后面注冊一個過濾器
- H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
- // 在過濾器鏈已有的beforeFilter類前面注冊一個過濾器
- H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);
- // 在過濾器鏈注冊一個過濾器,該過濾器必須在內置注冊表 FilterOrderRegistration 中
- H addFilter(Filter filter);
- }
HttpSecurityBuilder對DefaultSecurityFilterChain的構建進行了增強,為其構建器增加了一些額外的獲取配置或管理配置的入口,參見上面的注釋。補充一點這個接口最大的功能就是打通了構建和配置的關系,可以操作下面要講的SecurityConfigurer。
一句話,我只構建DefaultSecurityFilterChain。
SecurityConfigurer
SecurityConfigurer是對配置的抽象。配置只是手段,構建才是目的。因此配置是對構建的配置。
- public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
- // 構建器初始化需要注入的配置,用來后續的信息共享
- void init(B builder) throws Exception;
- // 其它的一些必要配置
- void configure(B builder) throws Exception;
- }
SecurityConfigurer有兩個方法,都非常重要。一個是init方法,這個方法你可以認為是SecurityBuilder構造函數的邏輯。如果你想在SecurityBuilder初始化的時候執行一些邏輯或者在后續配置中共享一些變量的話就可以在init方法中去實現;第二個方法是configure,為SecurityBuilder配置一些必要的屬性。到這里還沒完?這兩個方法有著明確的先后執行順序。在一次構建內可能有多個SecurityConfigurer,只有全部的init逐個執行完畢后才會逐個執行configure方法。相關的源碼在AbstractConfiguredSecurityBuilder中的標記部分:
- @Override
- protected final O doBuild() throws Exception {
- synchronized (this.configurers) {
- this.buildState = BuildState.INITIALIZING;
- beforeInit();
- // ① 執行所有的初始化方法
- init();
- this.buildState = BuildState.CONFIGURING;
- beforeConfigure();
- // ② 執行所有的configure方法
- configure();
- this.buildState = BuildState.BUILDING;
- O result = performBuild();
- this.buildState = BuildState.BUILT;
- return result;
- }
- }
一句話,配置SecurityBuilder的事都是我來干。
SecurityConfigurerAdapter
SecurityConfigurer在某些場景下是有局限性的,它不能獲取正在配置的SecurityBuilder,因此你無法進一步操作SecurityBuilder,配置的擴展性將大打折扣。因此引入了SecurityConfigurerAdapter來擴展SecurityConfigurer。
- public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
- private B securityBuilder;
- private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();
- @Override
- public void init(B builder) throws Exception {
- }
- @Override
- public void configure(B builder) throws Exception {
- }
- // 獲取正在配置的構建器,以暴露構建器的api
- public B and() {
- return getBuilder();
- }
- protected final B getBuilder() {
- Assert.state(this.securityBuilder != null, "securityBuilder cannot be null");
- return this.securityBuilder;
- }
- // 用復合對象后置處理器去處理對象,以改變一些對象的特性
- @SuppressWarnings("unchecked")
- protected <T> T postProcess(T object) {
- return (T) this.objectPostProcessor.postProcess(object);
- }
- // 添加一個ObjectPostProcessor到符合構建器
- public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
- this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
- }
- // 設置 需要配置的構建器,這樣可以讓多個SecurityConfigurerAdapter去配置一個SecurityBuilder
- public void setBuilder(B builder) {
- this.securityBuilder = builder;
- }
- // 其它省略
- }
這樣可以指定SecurityBuilder,而且可以把SecurityBuilder暴露出來,隨時隨地去調整SecurityBuilder,靈活性大大提高。
具體說的話,你可以通過and()方法獲取SecurityBuilder并對SecurityBuilder的其它配置項進行操作,比如上圖中SecurityConfigurerAdapter之間的切換。除此之外還引入了ObjectPostProcessor來后置操作一些并不開放的內置對象。關于ObjectPostProcessor會找個合適的場景去講解它。
一句話,配置SecurityBuilder不算什么,靈活適配才是花活。
AbstractHttpConfigurer
不是所有的配置都是有用的,有些配置我們希望有個關閉的入口功能。比如csrf功能,控制著csrf的配置的是CsrfConfigurer,如果CsrfConfigurer有一個關閉功能就好了。因此從SecurityConfigurerAdapter衍生出AbstractHttpConfigurer來滿足這個需求。
- public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
- extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
- // 關閉當前配置
- @SuppressWarnings("unchecked")
- public B disable() {
- getBuilder().removeConfigurer(getClass());
- return getBuilder();
- }
- // 增強了父類的新增ObjectPostProcessor方法
- @SuppressWarnings("unchecked")
- public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
- addObjectPostProcessor(objectPostProcessor);
- return (T) this;
- }
- }
AbstractHttpConfigurer的實現類非常多,日常的配置項大都由AbstractHttpConfigurer的實現類來控制。
這個類是做定制化配置的一個重要入口之一,如果你想精通Spring Security,這個類一定要掌握。
一句話,我能“殺”我自己。
AbstractConfiguredSecurityBuilder
我們希望有多個SecurityConfigurer配置SecurityBuilder,表單登錄的、會話管理、csrf等等。用到什么配置什么,讓配置基于策略。因此引入了AbstractConfiguredSecurityBuilder。
- public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
- // 把 objectPostProcessor注入到configurer
- configurer.addObjectPostProcessor(this.objectPostProcessor);
- // 為 SecurityConfigurerAdapter 設置Builder 以便于能夠get到
- // 注意區別于其它SecurityConfigurer
- configurer.setBuilder((B) this);
- add(configurer);
- return configurer;
- }
- public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
- add(configurer);
- return configurer;
- }
通過上面兩個apply方法就可以把所有的SecurityConfigurer適配進來,然后通過doBuilder進行精細化構建生命周期。你可以在各個生命周期階段進行一些必要的操作。
一句話,所有的配置都由我來進行適配。
總結
我們把Spring Security整個配置構建體系拆分了來看會簡單的多一些。即使這樣想理解這個體系也絕非靠一篇兩篇文章也是不現實的。不過從中也可以看得出一個道理,如果你的代碼想高度靈活,就必須把各個生命周期分層地高度抽象才行。
本文轉載自微信公眾號「碼農小胖哥」,可以通過以下二維碼關注。轉載本文請聯系碼農小胖哥公眾號。




































