??
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物件會有更加深刻的理解,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/501333.html
標籤:其他
上一篇:DP 優化方法合集
