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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...