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對象會有更加深刻的理解。