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
  • 前言 當別人做大數據用Java、Python的時候,我使用.NET做大數據、數據挖掘,這確實是值得一說的事。 寫的並不全面,但都是實際工作中的內容。 .NET在大數據項目中,可以做什麼? 寫腳本(使用控制台程式+頂級語句) 寫工具(使用Winform) 寫介面、寫服務 使用C#寫代碼的優點是什麼? ...
  • 前言 本文寫給想學C#的朋友,目的是以儘快的速度入門 C#好學嗎? 對於這個問題,我以前的回答是:好學!但仔細想想,不是這麼回事,對於新手來說,C#沒有那麼好學。 反而學Java還要容易一些,學Java Web就行了,就是SpringBoot那一套。 但是C#方向比較多,你是學控制台程式、WebAP ...
  • 某一日晚上上線,測試同學在回歸項目黃金流程時,有一個工單項目介面報JSF序列化錯誤,馬上升級對應的client包版本,編譯部署後錯誤消失。 線上問題是解決了,但是作為程式員要瞭解問題發生的原因和本質。但這都是為什麼呢? ...
  • 本文介紹基於Python語言中TensorFlow的Keras介面,實現深度神經網路回歸的方法。 1 寫在前面 前期一篇文章Python TensorFlow深度學習回歸代碼:DNNRegressor詳細介紹了基於TensorFlow tf.estimator介面的深度學習網路;而在TensorFl ...
  • 前段時間因業務需要完成了一個工作流組件的編碼工作。藉著這個機會跟大家分享一下整個創作過程,希望大家喜歡,組件暫且命名為"easyFlowable"。 接下來的文章我將從什麼是工作流、為什麼要自研這個工作流組件、架構設計三個維度跟大家來做個整體介紹。 ...
  • 1 簡介 我們之前使用了dapr的本地托管模式,但在生產中我們一般使用Kubernetes托管,本文介紹如何在GKE(GCP Kubernetes)安裝dapr。 相關文章: dapr本地托管的服務調用體驗與Java SDK的Spring Boot整合 dapr入門與本地托管模式嘗試 2 安裝GKE ...
  • 摘要:在jvm中有很多的參數可以進行設置,這樣可以讓jvm在各種環境中都能夠高效的運行。絕大部分的參數保持預設即可。 本文分享自華為雲社區《為什麼需要對jvm進行優化,jvm運行參數之標準參數》,作者:共飲一杯無。 我們為什麼要對jvm做優化? 在本地開發環境中我們很少會遇到需要對jvm進行優化的需 ...
  • 背景 我們的業務共使用11台(阿裡雲)伺服器,使用SpringcloudAlibaba構建微服務集群,共計60個微服務,全部註冊在同一個Nacos集群 流量轉發路徑: nginx->spring-gateway->業務微服務 使用的版本如下: spring-boot.version:2.2.5.RE ...
  • 基於php+webuploader的大文件分片上傳,帶進度條,支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況)。文件上傳前先檢測該文件是否已上傳,如果已上傳提示“文件已存在”,如果未上傳則直接上傳。視頻上傳時會根據設定的參數(分片大小、分片數量)進行上傳,上傳過程中會在目標文件夾中生成一個臨 ...
  • 基於php大文件分片上傳至七牛雲,使用的是七牛雲js-sdk V2版本,引入js文件,配置簡單,可以暫停,暫停後支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況),可以配置分片大小和分片數量,官方文檔https://developer.qiniu.com/kodo/6889/javascrip ...