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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...