Spring Security(二)--WebSecurityConfigurer配置以及filter順序

Ccww發表於2019-06-20

  在認證過程和訪問授權前必須瞭解spring Security如何知道我們要求所有使用者都經過身份驗證? Spring Security如何知道我們想要支援基於表單的身份驗證?因此必須瞭解WebSecurityConfigurerAdapter配置類如何工作的。而且也必須瞭解清楚filter的順序,才能更好了解其呼叫工作流程。

1. WebSecurityConfigurerAdapter

  在使用WebSecurityConfigurerAdapter前,先了解Spring security config。
  Spring security config具有三個模組,一共有3個builder,認證相關的AuthenticationManagerBuilder和web相關的WebSecurity、HttpSecurity。

  1. AuthenticationManagerBuilder:用來配置全域性的認證相關的資訊,其實就是AuthenticationProvider和UserDetailsService,前者是認證服務提供商,後者是使用者詳情查詢服務;

  2. WebSecurity: 全域性請求忽略規則配置(比如說靜態檔案,比如說註冊頁面)、全域性HttpFirewall配置、是否debug配置、全域性SecurityFilterChain配置、privilegeEvaluator、expressionHandler、securityInterceptor;

  3. HttpSecurity:具體的許可權控制規則配置。一個這個配置相當於xml配置中的一個標籤。各種具體的認證機制的相關配置,OpenIDLoginConfigurer、AnonymousConfigurer、FormLoginConfigurer、HttpBasicConfigurer等。

      WebSecurityConfigurerAdapter提供了簡潔方式來建立WebSecurityConfigurer,其作為基類,可通過實現該類自定義配置類,主要重寫這三個方法:

     protected void configure(AuthenticationManagerBuilder auth) throws Exception {}
     public void configure(WebSecurity web) throws Exception {}
     protected void configure(HttpSecurity httpSecurity) throws Exception {}
    複製程式碼

      而且其自動從SpringFactoriesLoader查詢AbstractHttpConfigurer讓我們去擴充套件,想要實現必須建立一個AbstractHttpConfigurer的擴充套件類,並在classpath路徑下建立一個檔案META-INF/spring.factories。例如:

    org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer

    其原始碼分析:

    //1.init初始化:獲取HttpSecurity和配置FilterSecurityInterceptor攔截器到WebSecurity
     public void init(final WebSecurity web) throws Exception {
             //獲取HttpSecurity
        final HttpSecurity http = getHttp();
     	//配置FilterSecurityInterceptor攔截器到WebSecurity
         web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
         	public void run() {
     	    	FilterSecurityInterceptor securityInterceptor = http
     			    	.getSharedObject(FilterSecurityInterceptor.class);
     	    	web.securityInterceptor(securityInterceptor);
     	    }
         });
     }
     ......
     //2.獲取HttpSecurity的過程
     protected final HttpSecurity getHttp() throws Exception {
     if (http != null) {
     	return http;
     }
    
     DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
     		.postProcess(new DefaultAuthenticationEventPublisher());
     localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
    
     AuthenticationManager authenticationManager = authenticationManager();
     authenticationBuilder.parentAuthenticationManager(authenticationManager);
     Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
    
     http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
     		sharedObjects);
     if (!disableDefaults) {
     	// 預設的HttpSecurity的配置
     	http
                     //新增 CSRF 支援,使用WebSecurityConfigurerAdapter時,預設啟用,禁用csrf().disable()
     		.csrf().and() 
     		//新增WebAsyncManagerIntegrationFilter
     		.addFilter(new WebAsyncManagerIntegrationFilter())
     		//允許配置異常處理
     		.exceptionHandling().and()
     		//將安全標頭新增到響應
     		.headers().and()
     		//允許配置會話管理
     		.sessionManagement().and()
     		//HttpServletRequest之間的SecurityContextHolder建立securityContext管理
     		.securityContext().and()
     		//允許配置請求快取
     		.requestCache().and()
     		//允許配置匿名使用者
     		.anonymous().and()
     		//HttpServletRequestd的方法和屬性註冊在SecurityContext中
     		.servletApi().and()
     		//使用預設登入頁面
     		.apply(new DefaultLoginPageConfigurer<>()).and()
     		//提供登出支援
     		.logout();
     	// @formatter:on
     	ClassLoader classLoader = this.context.getClassLoader();
     	List<AbstractHttpConfigurer> defaultHttpConfigurers =
     			SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
    
     	for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
     		http.apply(configurer);
     	}
     }
     configure(http);
     return http;
     }
     ...
     //3.可重寫方法實現自定義的HttpSecurity   
     protected void configure(HttpSecurity http) throws Exception {
     logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
    
     http
     	.authorizeRequests()
     		.anyRequest().authenticated()
     		.and()
     	.formLogin().and()
     	.httpBasic();
     }
     ....
    複製程式碼

      從原始碼init初始化模組中的“獲取HttpSecurity”和“配置FilterSecurityInterceptor攔截器到WebSecurity”中可以看出,想要spring Security如何知道我們要求所有使用者都經過身份驗證? Spring Security如何知道我們想要支援基於表單的身份驗證?只要重寫protected void configure(HttpSecurity http) throws Exception方法即可。因此我們需要理解HttpSecurity的方法的作用,如何進行配置。下一節來討論HttpSecurity。

    2. HttpSecurity

  HttpSecurity基於Web的安全性允許為特定的http請求進行配置。其有很多方法,列舉一些常用的如下表:

方法 說明 使用案例
csrf() 新增 CSRF 支援,使用WebSecurityConfigurerAdapter時,預設啟用 禁用:csrf().disable()
openidLogin() 用於基於 OpenId 的驗證 openidLogin().permitAll();
authorizeRequests() 開啟使用HttpServletRequest請求的訪問限制 authorizeRequests().anyRequest().authenticated()
formLogin() 開啟表單的身份驗證,如果未指定FormLoginConfigurer#loginPage(String),則將生成預設登入頁面 formLogin().loginPage("/authentication/login").failureUrl("/authentication/login?failed")
oauth2Login() 開啟OAuth 2.0或OpenID Connect 1.0身份驗證 authorizeRequests()..anyRequest().authenticated()..and().oauth2Login()
rememberMe() 開啟配置“記住我”的驗證 authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin().permitAll().and().rememberMe()
addFilter() 新增自定義的filter addFilter(new CustomFilter())
addFilterAt() 在指定filter相同位置上新增自定義filter addFilterAt(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
addFilterAfter() 在指定filter位置後新增自定義filter addFilterAfter(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
requestMatchers() 開啟配置HttpSecurity,僅當RequestMatcher相匹配時開啟 requestMatchers().antMatchers("/api/**")
antMatchers() 其可以與authorizeRequests()、RequestMatcher匹配,如:requestMatchers().antMatchers("/api/**")
logout() 新增退出登入支援。當使用WebSecurityConfigurerAdapter時,這將自動應用。預設情況是,訪問URL”/ logout”,使HTTP Session無效來清除使用者,清除已配置的任何#rememberMe()身份驗證,清除SecurityContextHolder,然後重定向到”/login?success” logout().deleteCookies("remove").invalidateHttpSession(false).logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success");

HttpSecurity還有很多方法供我們使用,去配置HttpSecurity。由於太多這邊就不一一說明,有興趣可去研究。

3. WebSecurityConfigurerAdapter使用

WebSecurityConfigurerAdapter示例:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 @Autowired
 private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
 protected void configure(HttpSecurity http) throws Exception {    
     http
     //request 設定
     .authorizeRequests()   //http.authorizeRequests() 方法中的自定義匹配
     .antMatchers("/resources/**", "/signup", "/about").permitAll() // 指定所有使用者進行訪問指定的url          
     .antMatchers("/admin/**").hasRole("ADMIN")  //指定具有特定許可權的使用者才能訪問特定目錄,hasRole()方法指定使用者許可權,且不需字首 “ROLE_“  
     .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")//          
     .anyRequest().authenticated()  //任何請求沒匹配的都需要進行驗證                                           
     .and()        //login設定  自定義登入頁面且允許所有使用者登入
     .formLogin()      
     .loginPage("/login") //The updated configuration specifies the location of the log in page  指定自定義登入頁面
     .permitAll(); // 允許所有使用者訪問登入頁面. The formLogin().permitAll() 方法
     .and 
     .logout()  //logouts 設定                                                              
     .logoutUrl("/my/logout")  // 指定登出路徑                                              
     .logoutSuccessUrl("/my/index") //指定成功登出後跳轉到指定的頁面                                        
     .logoutSuccessHandler(logoutSuccessHandler)  //指定成功登出後處理類 如果使用了logoutSuccessHandler()的話, logoutSuccessUrl()就會失效                                
     .invalidateHttpSession(true)  // httpSession是否有效時間,如果使用了 SecurityContextLogoutHandler,其將被覆蓋                                        
     .addLogoutHandler(logoutHandler)  //在最後增加預設的登出處理類LogoutHandler                
     .deleteCookies(cookieNamesToClear);//指定登出成功後remove cookies
     //增加在FilterSecurityInterceptor前新增自定義的myFilterSecurityInterceptor
     http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
   }
複製程式碼

NOTE:此示例只供參考

4. filter順序

Spring Security filter順序:

Filter Class 說明
ChannelProcessingFilter 訪問協議控制過濾器,可能會將我們重新定向到另外一種協議,從http轉換成https
SecurityContextPersistenceFilter 建立SecurityContext安全上下文資訊和request結束時清空SecurityContextHolder
ConcurrentSessionFilter 併發訪問控制過濾器,主要功能:SessionRegistry中獲取SessionInformation來判斷session是否過期,從而實現併發訪問控制。
HeaderWriterFilter 給http response新增一些Header
CsrfFilter 跨域過濾器,跨站請求偽造保護Filter
LogoutFilter 處理退出登入的Filter
X509AuthenticationFilter 新增X509預授權處理機制支援
CasAuthenticationFilter 認證filter,經過這些過濾器後SecurityContextHolder中將包含一個完全組裝好的Authentication物件,從而使後續鑑權能正常執行
UsernamePasswordAuthenticationFilter 認證的filter,經過這些過濾器後SecurityContextHolder中將包含一個完全組裝好的Authentication物件,從而使後續鑑權能正常執行。表單認證是最常用的一個認證方式。
BasicAuthenticationFilter 認證filter,經過這些過濾器後SecurityContextHolder中將包含一個完全組裝好的Authentication物件,從而使後續鑑權能正常執行
SecurityContextHolderAwareRequestFilter 此過濾器對ServletRequest進行了一次包裝,使得request具有更加豐富的API
JaasApiIntegrationFilter (JAAS)認證方式filter
RememberMeAuthenticationFilter 記憶認證處理過濾器,即是如果前面認證過濾器沒有對當前的請求進行處理,啟用了RememberMe功能,會從cookie中解析出使用者,並進行認證處理,之後在SecurityContextHolder中存入一個Authentication物件。
AnonymousAuthenticationFilter 匿名認證處理過濾器,當SecurityContextHolder中認證資訊為空,則會建立一個匿名使用者存入到SecurityContextHolder中
SessionManagementFilter 會話管理Filter,持久化使用者登入資訊,可以儲存到session中,也可以儲存到cookie或者redis中
ExceptionTranslationFilter 異常處理過濾器,主要攔截後續過濾器(FilterSecurityInterceptor)操作中丟擲的異常。
FilterSecurityInterceptor 安全攔截過濾器類,獲取當前請求url對應的ConfigAttribute,並呼叫accessDecisionManager進行訪問授權決策。

spring security的預設filter鏈:

 SecurityContextPersistenceFilter
->HeaderWriterFilter
->LogoutFilter
->UsernamePasswordAuthenticationFilter
->RequestCacheAwareFilter
->SecurityContextHolderAwareRequestFilter
->SessionManagementFilter
->ExceptionTranslationFilter
->FilterSecurityInterceptor
複製程式碼

在上節我們已分析了核心的filter原始碼以及功能。可回看上節原始碼分析更加深入的瞭解各個filter工作原理。

總結:

  在認證和訪問授權過程前,首先必須進行WebSecurityConfigurer符合自身應用的security Configurer,也要清楚filter鏈的先後順序,才能更好理解spring security的工作原理以及在專案中出現的問題定位。瞭解完準備工作,接下來將展開對認證和訪問授權模組的工作流程研究以及專案示例分析。最後如有錯誤可評論告知。

相關文章