主頁 > 移動端開發 > Spring Security最難的地方就是這個了

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

2021-12-30 10:16:04 移動端開發

bcdb63c6537b4b3528c11c3f366afc4a.gif

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

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

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
        implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
    // 省略
}

感覺不到的話,再給你看看UML圖:6762361f34d4407d933e11e50ee50760.png

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

配置與構建分離

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

SecurityBuilder

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

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

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

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

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

AbstractSecurityBuilder

AbstractSecurityBuilder是對SecurityBuilder的實作,原始碼如下:

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

}

HttpSecurityBuilderDefaultSecurityFilterChain的構建進行了增強,為其構建器增加了一些額外的獲取配置或管理配置的入口,參見上面的注釋,補充一點這個介面最大的功能就是打通了構建和配置的關系,可以操作下面要講的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,靈活性大大提高,61038ad09259e2000594ab4e2134546e.png具體說的話,你可以通過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的實作類來控制,5bd26d0c9e6e2e5cd7625b07b367e124.png這個類是做定制化配置的一個重要入口之一,如果你想精通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進行精細化構建生命周期,你可以在各個生命周期階段進行一些必要的操作,

f8b0eba7fe8ff2b766238a9a05645dab.png
?

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

總結

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

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

2021-12-15

dced51ad988f58ec64a9071935bcf055.png

更快的Maven來了

2021-12-23

5289388783cf46eb70f99a05ffc9ecd4.png

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

2021-12-22

8aa4f8a9769639a2fce80890b27c5583.png

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

2021-12-22

3c91f031b194b7fe0dd15973306e505a.png

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

2021-12-20

9a81bf175430cf3cc9deb9c3e9452e3a.png

6be26846dec110c51e520bbcd2461241.gif

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/397404.html

標籤:其他

上一篇:滿滿的WebView優化詳解,讓你的H5實作秒開體驗。

下一篇:GSYVideoPlayer實作視頻播放

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more