Spring Security最難的地方就是這個了

      網友投稿 1029 2025-03-31

      本篇摘自胖哥最新的基于Spring Security 5.6.x的《Spring Security干貨》教程。舊版的教程將在2022年1月1日下線,請需要的同學盡快通過本公眾號回復“2021開工福利”下載。原創不易,請多多關注、、轉發。

      Spring Security最難的地方就是HttpSecurity的頂層設計。不信你看看HttpSecurity的定義。

      public?final?class?HttpSecurity?extends?AbstractConfiguredSecurityBuilder ????????implements?SecurityBuilder,?HttpSecurityBuilder?{ ????//?省略 }

      感覺不到的話,再給你看看UML圖:

      為什么要這么復雜?我第一次看到HttpSecurity的結構時我懷疑我自己是不是Java開發。多年以后,當我深入學習了之后才理解了這種設計。作為一個框架,尤其是安全框架,配置必須足夠靈活才能適用于更多的業務場景。Spring Security采取了配置與構建分離的架構設計來保證這一點。

      配置與構建分離

      配置只需要去收集配置項,構建只需要把所有的配置構建成目標對象。各干各的,分離職責,這種做法能夠提高代碼的可維護性和可讀寫性。Spring Security利用接口隔離把配置和構建進行高度抽象,提高靈活度,降低復雜度。不過這個體系依然非常龐大。為了降低學習難度需要把大問題拆解成小問題,各個擊破,這種學習方法在學習一些復雜的抽象理論時很湊效。

      SecurityBuilder

      SecurityBuilder就是對構建的抽象。你看上面的類圖過于復雜,而看SecurityBuilder就非常的簡單了。

      public?interface?SecurityBuilder?{ ????//?構建 ?O?build()?throws?Exception; }

      就一個動作,構建泛化的目標對象O。通過下面這一組抽象和具體的定義我想你應該明白SecurityBuilder了吧。

      //?抽象 ?SecurityBuilder?->?O ?//?具體 ?HttpSecurity->DefaultSecurityFilterChain

      一句話,構建的活都是我來干。

      AbstractSecurityBuilder

      AbstractSecurityBuilder是對SecurityBuilder的實現。源碼如下:

      public?abstract?class?AbstractSecurityBuilder?implements?SecurityBuilder?{ ?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> ??extends?SecurityBuilder?{ ?????//?根據類名獲取配置?? ?>?C?getConfigurer(Class?clazz); ????//?根據類名移除配置? ?>?C?removeConfigurer(Class?clazz); ????//?把某個對象設置為共享,以便于在多個SecurityConfigurer中使用 ??void?setSharedObject(Class?sharedType,?C?object); ????//?獲取某個共享對象 ??C?getSharedObject(Class?sharedType); ????//??添加額外的?AuthenticationProvider ?H?authenticationProvider(AuthenticationProvider?authenticationProvider); ????//??添加額外的?UserDetailsService ?H?userDetailsService(UserDetailsService?userDetailsService)?throws?Exception; ????//?在過濾器鏈已有的afterFilter類后面注冊一個過濾器 ?H?addFilterAfter(Filter?filter,?Class?afterFilter); ????//?在過濾器鏈已有的beforeFilter類前面注冊一個過濾器 ?H?addFilterBefore(Filter?filter,?Class?beforeFilter); ????//?在過濾器鏈注冊一個過濾器,該過濾器必須在內置注冊表?FilterOrderRegistration?中 ?H?addFilter(Filter?filter); }

      HttpSecurityBuilder對DefaultSecurityFilterChain的構建進行了增強,為其構建器增加了一些額外的獲取配置或管理配置的入口,參見上面的注釋。補充一點這個接口最大的功能就是打通了構建和配置的關系,可以操作下面要講的SecurityConfigurer。

      一句話,我只構建DefaultSecurityFilterChain。

      SecurityConfigurer是對配置的抽象。配置只是手段,構建才是目的。因此配置是對構建的配置。

      public?interface?SecurityConfigurer>?{ ???//?構建器初始化需要注入的配置,用來后續的信息共享 ?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>?implements?SecurityConfigurer?{ ????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?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,?B?extends?HttpSecurityBuilder> ????????extends?SecurityConfigurerAdapter?{ ????//?關閉當前配置 ????@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?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?apply(C?configurer)?throws?Exception?{ ??add(configurer); ??return?configurer; ?}

      通過上面兩個apply方法就可以把所有的SecurityConfigurer適配進來,然后通過doBuilder進行精細化構建生命周期。你可以在各個生命周期階段進行一些必要的操作。

      一句話,所有的配置都由我來進行適配。

      總結

      我們把Spring Security整個配置構建體系拆分了來看會簡單的多一些。即使這樣想理解這個體系也絕非靠一篇兩篇文章也是不現實的。不過從中也可以看得出一個道理,如果你的代碼想高度靈活,就必須把各個生命周期分層地高度抽象才行。

      2021版Spring Security實戰干貨教程即將下線,2022版即將上線!

      2021-12-15

      更快的Maven來了

      2021-12-23

      能進這個Java組織的都是大神,現在只有三個中國人

      2021-12-22

      Spring Security最難的地方就是這個了

      如果你是頭條用戶,一定要加入這個程序員組織

      2021-12-22

      前瞻|Spring 6.0將停止支持Freemarker和JSP

      2021-12-20

      Spring

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:實用Excel技巧分享:巧用【Ctrl】和【Shift】快捷鍵(excel ctrl+shift)
      下一篇:如何在Word中將頁面從一個文檔移動/復制到另一文檔或新文檔?
      相關文章
      亚洲欧洲无码AV电影在线观看| 4480yy私人影院亚洲| 亚洲综合久久1区2区3区| 亚洲国产精品嫩草影院在线观看 | 久久亚洲精品高潮综合色a片| 在线精品亚洲一区二区| 亚洲综合欧美色五月俺也去| 最新亚洲精品国偷自产在线| 亚洲午夜无码久久久久软件 | 亚洲国产电影av在线网址| 色窝窝亚洲AV网在线观看| 亚洲AV无码一区二区一二区| 香蕉视频亚洲一级| 亚洲AⅤ视频一区二区三区| 婷婷亚洲综合五月天小说在线 | 精品国产亚洲第一区二区三区| 国产精品亚洲一区二区三区在线观看| 亚洲av日韩综合一区久热| 日韩在线视精品在亚洲| 亚洲国产成人久久精品99 | 国产精品亚洲二区在线观看| 在线A亚洲老鸭窝天堂| 亚洲精品亚洲人成在线观看| 亚洲avav天堂av在线不卡| 久久久久亚洲AV成人片| 亚洲伊人久久精品| 亚洲一区免费在线观看| 亚洲色大成WWW亚洲女子| 亚洲色大成网站www永久网站| 色窝窝亚洲av网| 中文字幕亚洲一区| 亚洲AV无码欧洲AV无码网站| 亚洲视频在线不卡| 亚洲а∨天堂久久精品9966| 婷婷亚洲综合一区二区| 一本久久a久久精品亚洲| 亚洲成熟xxxxx电影| 亚洲一区二区三区在线| 亚洲JIZZJIZZ妇女| 中文亚洲成a人片在线观看| 久久久久亚洲精品成人网小说|