ObjectPostProcessor使用與多種用戶定義方式(9)

来源:https://www.cnblogs.com/liwenruo/archive/2022/08/09/16570659.html
-Advertisement-
Play Games

1.ObjectPostProcessor 使用 前面介紹了 ObjectPostProcessor的基本概念。相信讀者已經明白,所有的過濾器都由對應的配置類來負責創建,配置類在將過濾器創建成功之後,會調用父類的postProcess方法,該 方法最終會調用到CompositeObjectPostP ...


  

1.ObjectPostProcessor 使用

  前面介紹了 ObjectPostProcessor的基本概念。相信讀者已經明白,所有的過濾器都由對應的配置類來負責創建,配置類在將過濾器創建成功之後,會調用父類的postProcess方法,該 方法最終會調用到CompositeObjectPostProcessor對象的postProcess方法,在該方法中,會遍 歷 CompositeObjectPostProcessor 對象所維護的 List 集合中存儲的所有 ObjectPostProcessor 對 象,並調用其postProcess方法對對象進行後置處理。預設情況下,CompositeObjectPostProcessor 對象中所維護的List集合中只有一個對象那就是AutowireBeanFactoryObjectPostProcessor調用 AutowireBeanFactoryObjectPostProcessor 的 postProcess 方法可以將對象註冊到 Spring 容器 中去。

  升發者可以自定義ObjectPostProcessor對象,並添加到CompositeObjectPostProcessor所維護的List集合中,此時,當一個過濾器在創建成功之後,就會被兩個對象後置處理器處理, 第一個是預設的對象後置處理器,負責將對象註冊到Spring容器中;第二個是我們自定義的對象後置處理器,可以完成一些個性化配置.

  自定義ObjectPostProcessor對象比較典型的用法是動態許可權配置(許可權管理將在後續章節 具體介紹),為了便於大家理解,筆者這裡先通過一個大家熟悉的案例來展示 ObjectPostProcessor的用法,後面在配置動態許可權時,ObjectPostProcessor的使用思路是一樣的。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>(){
            @Override
            public <O extends UsernamePasswordAuthenticationFilter> O postProcess(O object) {
                object.setUsernameParameter("name");
                object.setPasswordParameter("passwd");
                object.setAuthenticationSuccessHandler(((request, response, authentication) -> {
                    response.getWriter().write("login Success");
                }));
                return object;
            }
        })
        .and()
        .csrf().disable();
}

  在這個案例中,調用formLogin方法之後,升啟了 FormLoginConfigurer的配置,FormLoginConfigurer 的作用是為了配置 UsernamePasswordAuthenticationFilter 過濾器,在 formLogin 方法執行完畢後,我們調用 withObjectPostProcessor 方法對 UsernamePasswordAuthenticationFilter 過濾器進行二次處理,修改登錄參數的key,將登錄用戶名參數的key改為name,將登錄密碼參數的key改為passwd,同時配置一個登錄成功的處理器。

  2.多種用戶定義方式

在前面的章節中,我們定義用戶主要是兩種方式:

  (1)第一種方式是使用的重寫configure(AuthenticationManagerBuilder)方法的方式。

  (2)第二種方式是定義多個數據源時,我們直接向Spring容器中註入了 UserDetailsService 對象。

那麼這兩種用戶定義方式有什麼區別?

  根據前面的源碼分析可知,在Spring Security中存在兩種類型的AuthenticationManager, 一種是全局的AuthenticationManager,另一種則是局部的AuthenticationManager局部的 AuthenticationManager,由 HttpSecurity 進行配置,而全局的 AuthenticationManager 可以不用配置,系統會預設提供一個全局的AuthenticationManager對象,也可以通過重寫 configure(AuthenticationMaiiagerBuilder)方法進行全局配置。

  當進行用戶身份驗證時,首先會通過局部的AuthenticationManager對象進行驗證,如果驗證失敗,則會調用其parent也就是全局的AuthenticationManager再次進行驗證。

  所以開發者在定義用戶時,也分為兩種情況,一種是針對局部AuthenticationManager定義的用戶,另一種則是針對全局AuthenticationManager定義的用戶。

  為了演示方便,接下來的案例我們將採用InMemoryUserDetailsManager來構建用戶對象, 讀者也可以自行使用基於MyBatis或者Spring Data JPA定義的UserDetailsService實例。

先來看針對局部AuthenticationManager定義的用戶:

@Override
protected void configure(HttpSecurity http) throws Exception {
    InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
    users.createUser(User.withUsername("buretuzi").password("{noop}123").roles("admin").build());
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .permitAll()
        .and()
        .userDetailsService(users)
        .csrf().disable();
}

  在上面這段代碼中,我們基於記憶體來管理用戶,並向users中添加了一個用戶,將配置好的users對象添加到HttpSecurity中,也就是配置到局部的AuthenticationManager中。

  配置完成後,啟動項目。項目啟動成功後,我們就可以使用buretuzi/123來登錄系統了。 但是註意,當我們啟動項目時,在IDEA控制台輸出的日誌中可以看到如下內容:

  Using generated security password: cfc7f8b5-8346-492e-b25c-90c2c4501350

  這個是系統自動生成的用戶,那麼我們是否可以使用系統自動生成的用戶進行登錄呢?答案是可以的,為什麼呢?

  系統自動提供的用戶對象實際上就是往Spring容器中註冊了一個 InMemoryUserDetailsManager 對象. 而在前面的代碼中,我們沒有重寫 configure(AuthenticationManagerBuilder)方法,這意味著全局的 AuthenticationManager 是通過 AuthenticationConfignration#getAuthenticationManager 方法自動生成的,在生成的過程中,會 從Spring容器中查找對應的UserDetailsService實例進行配置(具體配置在InitializeUserDetailsManagerConfigurer類中)。所以系統自動提供的用戶實際上相當於是全局AuthenticationManager對應的用戶。

  以上面的代碼為例,當我們開始執行登錄後,Spring Security首先會調用局部Authentication Manager去進行登錄校驗,如果登錄的用戶名/密碼是buretuzi/123,那就直接登錄成功,否則登錄失敗,當登錄失敗後,會繼續調用局部AuthenticationManager的parent繼續進行校驗,此時如果登錄的用戶名/密碼是user/cfc7f8b5-8346-492e-b25c-90c2c4501350,則登錄成功,否則登錄失敗。

  這是針對局部AuthenticationManager定義的用戶,我們也可以將定義的用戶配置給全局 的AuthenticationManager,由於預設的全局AuthenticationManager在配置時會從Spring容器中 査找UserDetailsService實例,所以我們如果針對全局AuthenticationManager配置用戶,只需要往Spring容器中註入一個UserDetailsService實例即可,代碼如下:

@Bean
UserDetailsService us(){
    InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
    users.createUser(User.withUsername("李文若").password("{noop}123").roles("admin").build());
    return users;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
    InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
    users.createUser(User.withUsername("buretuzi").password("{noop}123").roles("admin").build());
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .permitAll()
        .and()
        .userDetailsService(users)
        .csrf().disable();
}

   配置完成後,當我們啟動項目時,全局的AuthenticationManager在配置時會去Spring容器中查找UserDetailsService實例,找到的就是我們自定義的UserDetailsService實例。當我們進行登錄時,系統拿著我們輸入的用戶名/密碼,首先和buretuzi/123進行匹配,如果匹配不上的話,再去和 李文若/123進行匹配。

  當然,升發者也可以不使用Spring Security提供的預設的全局AuthenticationManager對象, 而是通過重寫 Configure(AuthenticationManagerBuilder)方法來自定義全局 Authentication Manager 對象:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("劍氣近").password("{noop}123").roles("admin");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
    InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
    users.createUser(User.withUsername("buretuzi").password("{noop}123").roles("admin").build());
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .permitAll()
        .and()
        .userDetailsService(users)
        .csrf().disable();
}

  根據對 WebSecurityConfigurerAdapter的源碼分析可知,一旦我們重寫了 configure(AuthenticationManagerBuilder)方法,則全局的 AuthenticationManager 對象將不再通過 AuthenticationConfiguration#getAuthenticationManager 方法來構建,而是通過 WebSecurityConfigiuerAdapter中的localConfigureAuthenticationBuilder變數來構建,該變數也是我們重寫的 configure(AuthenticationManagerBuilder)方法的參數。

  配置完成後,當我們啟動項目時,全局的AuthenticationManager在構建時會直接使用 configure(AutheuticationManagerBuilder)方法的auth變數去構建,使用的用戶也是我們配置給 auth變數的用戶,當我們進行登錄時,系統會將所輸入的用戶名/密碼,首先和buretuzi/123進 行匹配,如果匹配不上的話,再去和 劍氣近/123進行匹配。

  需要註意的是,一旦重寫了 configure(AuthenticationManagerBuilder)方法,那麼全局 AuthenticationManager對象中使用的用戶,將以 configure(AuthenticationManagerBuilder)方法中定義的用戶為準。此時,如果我們還向Spring容器中註入了另外一個UserDetailsService實例,那麼該實例中定義的用戶將不會生效(因為 AuthenticationConfiguration#getAuthenticationManager方法沒有被調用)。

  這就是Spring Security中幾種不同的用戶定義方式,相信通過這幾個案例,讀者對於全局 AuthenticationManager和局部AuthenticationManager對象會有更加深刻的理解。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 作為一名Java Developer,我們都清楚地知道,主要從搭載Linux系統上的伺服器程式來說,使用Java編寫的是”單進程-多線程"程式,而用C++語言編寫的,可能是“單進程-多線程”程式,“多 ...
  • 技術 Leader 是一個對綜合素質要求非常高的崗位,不僅要有解具體技術問題的架構能力,還要具備團隊管理的能力,更需要引領方向帶領團隊/平臺穿越迷茫進階到下一個境界的能力。所以通常來說技術 Leader 的技能是虛實結合的居多,繁雜的工作偏多。為此我把自己在工作中經常用到的思考技巧也做了一個整理。 ...
  • 社交是一種永恆的需求,既有生存層面的必要,也有情感上的渴求。而隨著互聯網開始統治這個時代,社交被搬到了網上,並且越來越成為主流,社交也在發展成互聯網產品的一個重要賽道。本文將介紹Soul是如何破解Z世代社交密碼的。 文章目錄 01 年輕人的社交密碼 02 為什麼對年輕人來說,Soul是那個對的產品? ...
  • 統一術語(戰略設計) 我們將通過DDD完成業務與技術的完整落地 統一 領域模型術語 DDD模式名稱 技術 技術設計術語 技術術語 技術設計模式 業務 領域模型術語 DDD模式名稱 業務術語 設計無關的業務術語 清晰的事件流 DDD 領域驅動設計是一個有關軟體開發的方法論,它提出基於領域開發的開發模式 ...
  • 3、ElasticSearch搜索結果處理 3.1、排序 Elasticsearch預設是根據相關度算分(_score)來排序,但是也支持自定義方式對搜索結果排序,可以排序的欄位類型有如下幾種 keyword類型 數值類型 地理坐標類型 日期類型 ... 3.1.1、普通欄位排序 keyword、數 ...
  • @Autowired註解是spring用來支持依賴註入的核心利器之一,但是我們或多或少都會遇到required a single bean, but 2 were found(2可能是其他數字)的問題,接下來我們從源碼的角度去看為什麼會出現這個問題,以及這個問題的解法是什麼? 首先我們寫一個demo ...
  • 2、ElasticSearch高級搜索 Elasticsearch提供了基於JSON的DSL(Domain Specific Language)來定義查詢。常見的查詢類型如下所示 ①、查詢所有 查詢出所有數據,一般測試用;例如 match_all 如下圖所示 ②、全文檢索(full text)查詢 ...
  • Java多線程基礎入門 參考:b站-狂神-多線程詳解 練習與演示代碼見gitee:https://gitee.com/yuhaozhee/java-learning-record ...
一周排行
    -Advertisement-
    Play Games
  • MQTTnet 是一個高性能的MQTT類庫,支持.NET Core和.NET Framework。 MQTTnet 原理: MQTTnet 是一個用於.NET的高性能MQTT類庫,實現了MQTT協議的各個層級,包括連接、會話、發佈/訂閱、QoS(服務質量)等。其原理涉及以下關鍵概念: MqttCli ...
  • 在WPF中,源屬性(Source Property)指的是提供數據的屬性,通常是數據模型或者其他控制項的屬性,而目標屬性(Target Property)則是數據綁定的目標,通常是綁定到控制項的屬性,例如TextBlock的Text屬性。數據綁定將源屬性的值自動更新到目標屬性中。 主要包含以下幾個事件: ...
  • async/await 是 C# 中非同步編程的關鍵特性,它使得非同步代碼編寫更為簡單和直觀。下麵深入詳細描述了 async/await 的使用場景、優點以及一些高級使用方法,並提供了相應的實例源代碼。 使用場景: I/O 操作: 非同步編程特別適用於涉及 I/O 操作(如文件讀寫、網路請求等)的場景。在 ...
  • 使用過office的visio軟體畫圖的小伙伴都知道,畫圖軟體分為兩部分,左側圖形庫,存放各種圖標,右側是一個畫布,將左側圖形庫的圖標控制項拖拽到右側畫布,就會生成一個新的控制項,並且可以自由拖動。那如何在WPF程式中,實現類似的功能呢?今天就以一個簡單的小例子,簡述如何在WPF中實現控制項的拖拽和拖動,... ...
  • 1、Blazor Hybrid簡介 Blazor Hybrid 使開發人員能夠將桌面和移動本機客戶端框架與 .NET 和 Blazor 結合使用。在 Blazor Hybrid 應用中,Razor 組件在設備上是本機運行的。 這些組件通過本地互操作通道呈現到嵌入式 Web 視圖控制項。 組件不在瀏覽器 ...
  • 除了內置的數據集,scikit-learn還提供了隨機樣本的生成器。通過這些生成器函數,可以生成具有特定特性和分佈的隨機數據集,以幫助進行機器學習演算法的研究、測試和比較。 目前,scikit-learn庫(v1.3.0版)中有20個不同的生成樣本的函數。本篇重點介紹其中幾個具有代表性的函數。 1. ...
  • 從0到1,手把手帶你開發截圖工具ScreenCap------002實現通過文件對話框,選擇合適的文件夾,自定義預設的圖片保存位置,簡單易學 ...
  • 每次談到容器的時候,除了Docker之外,都會說起 Kubernetes,那麼什麼是 Kubernetes呢?今天就來一起學快速入門一下 Kubernetes 吧!希望本文對您有所幫助。 Kubernetes,一種用於管理和自動化雲中容器化工作負載的工具。 想象一下你有一個管弦樂隊,將每個音樂家視為 ...
  • 目錄 基本說明 安裝 Nginx 部署 VUE 前端 部署 Django 後端 Django admin 靜態文件(CSS,JS等)丟失的問題 總結 1. 基本說明 本文介紹了在 windows 伺服器下,通過 Nginx 部署 VUE + Django 前後端分離項目。本項目前端運行在 80 埠 ...
  • 從0到1,手把手帶你開發截圖工具ScreenCap------003實現最小化程式到托盤運行,- 為了方便截圖乾凈,實現最小化程式到托盤運行,簡潔,勿擾,實現最小化程式到托盤運行, 實現托盤菜單功能,實現回顯主窗體, 實現托盤開始截屏, 實現氣泡信息提示,實現托盤程式提示,實現托盤退出程式, 封裝完... ...