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

??在認(rèn)證過(guò)程和訪問(wèn)授權(quán)前必須了解spring Security如何知道我們要求所有用戶都經(jīng)過(guò)身份驗(yàn)證? Spring Security如何知道我們想要支持基于表單的身份驗(yàn)證?因此必須了解WebSecurityConfigurerAdapter配置類如何工作的。而且也必須了解清楚filter的順序,才能更好了解其調(diào)用工作流程。

1. WebSecurityConfigurerAdapter

??在使用WebSecurityConfigurerAdapter前,先了解Spring security config。
??Spring security config具有三個(gè)模塊,一共有3個(gè)builder,認(rèn)證相關(guān)的AuthenticationManagerBuilder和web相關(guān)的WebSecurity、HttpSecurity。

  1. AuthenticationManagerBuilder:用來(lái)配置全局的認(rèn)證相關(guān)的信息,其實(shí)就是AuthenticationProvider和UserDetailsService,前者是認(rèn)證服務(wù)提供商,后者是用戶詳情查詢服務(wù);

  2. WebSecurity: 全局請(qǐng)求忽略規(guī)則配置(比如說(shuō)靜態(tài)文件,比如說(shuō)注冊(cè)頁(yè)面)、全局HttpFirewall配置、是否debug配置、全局SecurityFilterChain配置、privilegeEvaluator、expressionHandler、securityInterceptor;

  3. HttpSecurity:具體的權(quán)限控制規(guī)則配置。一個(gè)這個(gè)配置相當(dāng)于xml配置中的一個(gè)標(biāo)簽。各種具體的認(rèn)證機(jī)制的相關(guān)配置,OpenIDLoginConfigurer、AnonymousConfigurer、FormLoginConfigurer、HttpBasicConfigurer等。

    ??WebSecurityConfigurerAdapter提供了簡(jiǎn)潔方式來(lái)創(chuàng)建WebSecurityConfigurer,其作為基類,可通過(guò)實(shí)現(xiàn)該類自定義配置類,主要重寫這三個(gè)方法:

     protected void configure(AuthenticationManagerBuilder auth) throws Exception {}
     public void configure(WebSecurity web) throws Exception {}
     protected void configure(HttpSecurity httpSecurity) throws Exception {}
    

    ??而且其自動(dòng)從SpringFactoriesLoader查找AbstractHttpConfigurer讓我們?nèi)U(kuò)展,想要實(shí)現(xiàn)必須創(chuàng)建一個(gè)AbstractHttpConfigurer的擴(kuò)展類,并在classpath路徑下創(chuàng)建一個(gè)文件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的過(guò)程
     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) {
         // 默認(rèn)的HttpSecurity的配置
         http
                     //添加 CSRF 支持,使用WebSecurityConfigurerAdapter時(shí),默認(rèn)啟用,禁用csrf().disable()
             .csrf().and() 
             //添加WebAsyncManagerIntegrationFilter
             .addFilter(new WebAsyncManagerIntegrationFilter())
             //允許配置異常處理
             .exceptionHandling().and()
             //將安全標(biāo)頭添加到響應(yīng)
             .headers().and()
             //允許配置會(huì)話管理
             .sessionManagement().and()
             //HttpServletRequest之間的SecurityContextHolder創(chuàng)建securityContext管理
             .securityContext().and()
             //允許配置請(qǐng)求緩存
             .requestCache().and()
             //允許配置匿名用戶
             .anonymous().and()
             //HttpServletRequestd的方法和屬性注冊(cè)在SecurityContext中
             .servletApi().and()
             //使用默認(rèn)登錄頁(yè)面
             .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.可重寫方法實(shí)現(xiàn)自定義的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如何知道我們要求所有用戶都經(jīng)過(guò)身份驗(yàn)證? Spring Security如何知道我們想要支持基于表單的身份驗(yàn)證?只要重寫protected void configure(HttpSecurity http) throws Exception方法即可。因此我們需要理解HttpSecurity的方法的作用,如何進(jìn)行配置。下一節(jié)來(lái)討論HttpSecurity。

    2. HttpSecurity

??HttpSecurity基于Web的安全性允許為特定的http請(qǐng)求進(jìn)行配置。其有很多方法,列舉一些常用的如下表:

方法 說(shuō)明 使用案例
csrf() 添加 CSRF 支持,使用WebSecurityConfigurerAdapter時(shí),默認(rèn)啟用 禁用:csrf().disable()
openidLogin() 用于基于 OpenId 的驗(yàn)證 openidLogin().permitAll();
authorizeRequests() 開(kāi)啟使用HttpServletRequest請(qǐng)求的訪問(wèn)限制 authorizeRequests().anyRequest().authenticated()
formLogin() 開(kāi)啟表單的身份驗(yàn)證,如果未指定FormLoginConfigurer#loginPage(String),則將生成默認(rèn)登錄頁(yè)面 formLogin().loginPage("/authentication/login").failureUrl("/authentication/login?failed")
oauth2Login() 開(kāi)啟OAuth 2.0或OpenID Connect 1.0身份驗(yàn)證 authorizeRequests()..anyRequest().authenticated()..and().oauth2Login()
rememberMe() 開(kāi)啟配置“記住我”的驗(yàn)證 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() 開(kāi)啟配置HttpSecurity,僅當(dāng)RequestMatcher相匹配時(shí)開(kāi)啟 requestMatchers().antMatchers("/api/**")
antMatchers() 其可以與authorizeRequests()、RequestMatcher匹配,如:requestMatchers().antMatchers("/api/**")
logout() 添加退出登錄支持。當(dāng)使用WebSecurityConfigurerAdapter時(shí),這將自動(dòng)應(yīng)用。默認(rèn)情況是,訪問(wèn)URL”/ logout”,使HTTP Session無(wú)效來(lái)清除用戶,清除已配置的任何#rememberMe()身份驗(yàn)證,清除SecurityContextHolder,然后重定向到”/login?success” logout().deleteCookies("remove").invalidateHttpSession(false).logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success");

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

3. WebSecurityConfigurerAdapter使用

WebSecurityConfigurerAdapter示例:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 @Autowired
 private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
 protected void configure(HttpSecurity http) throws Exception {    
     http
     //request 設(shè)置
     .authorizeRequests()   //http.authorizeRequests() 方法中的自定義匹配
     .antMatchers("/resources/**", "/signup", "/about").permitAll() // 指定所有用戶進(jìn)行訪問(wèn)指定的url          
     .antMatchers("/admin/**").hasRole("ADMIN")  //指定具有特定權(quán)限的用戶才能訪問(wèn)特定目錄,hasRole()方法指定用戶權(quán)限,且不需前綴 “ROLE_“  
     .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")//          
     .anyRequest().authenticated()  //任何請(qǐng)求沒(méi)匹配的都需要進(jìn)行驗(yàn)證                                           
     .and()        //login設(shè)置  自定義登錄頁(yè)面且允許所有用戶登錄
     .formLogin()      
     .loginPage("/login") //The updated configuration specifies the location of the log in page  指定自定義登錄頁(yè)面
     .permitAll(); // 允許所有用戶訪問(wèn)登錄頁(yè)面. The formLogin().permitAll() 方法
     .and 
     .logout()  //logouts 設(shè)置                                                              
     .logoutUrl("/my/logout")  // 指定注銷路徑                                              
     .logoutSuccessUrl("/my/index") //指定成功注銷后跳轉(zhuǎn)到指定的頁(yè)面                                        
     .logoutSuccessHandler(logoutSuccessHandler)  //指定成功注銷后處理類 如果使用了logoutSuccessHandler()的話, logoutSuccessUrl()就會(huì)失效                                
     .invalidateHttpSession(true)  // httpSession是否有效時(shí)間,如果使用了 SecurityContextLogoutHandler,其將被覆蓋                                        
     .addLogoutHandler(logoutHandler)  //在最后增加默認(rèn)的注銷處理類LogoutHandler                
     .deleteCookies(cookieNamesToClear);//指定注銷成功后remove cookies
     //增加在FilterSecurityInterceptor前添加自定義的myFilterSecurityInterceptor
     http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
   }

NOTE:此示例只供參考

4. filter順序

Spring Security filter順序:

Filter Class 說(shuō)明
ChannelProcessingFilter 訪問(wèn)協(xié)議控制過(guò)濾器,可能會(huì)將我們重新定向到另外一種協(xié)議,從http轉(zhuǎn)換成https
SecurityContextPersistenceFilter 創(chuàng)建SecurityContext安全上下文信息和request結(jié)束時(shí)清空SecurityContextHolder
ConcurrentSessionFilter 并發(fā)訪問(wèn)控制過(guò)濾器,主要功能:SessionRegistry中獲取SessionInformation來(lái)判斷session是否過(guò)期,從而實(shí)現(xiàn)并發(fā)訪問(wèn)控制。
HeaderWriterFilter 給http response添加一些Header
CsrfFilter 跨域過(guò)濾器,跨站請(qǐng)求偽造保護(hù)Filter
LogoutFilter 處理退出登錄的Filter
X509AuthenticationFilter 添加X(jué)509預(yù)授權(quán)處理機(jī)制支持
CasAuthenticationFilter 認(rèn)證filter,經(jīng)過(guò)這些過(guò)濾器后SecurityContextHolder中將包含一個(gè)完全組裝好的Authentication對(duì)象,從而使后續(xù)鑒權(quán)能正常執(zhí)行
UsernamePasswordAuthenticationFilter 認(rèn)證的filter,經(jīng)過(guò)這些過(guò)濾器后SecurityContextHolder中將包含一個(gè)完全組裝好的Authentication對(duì)象,從而使后續(xù)鑒權(quán)能正常執(zhí)行。表單認(rèn)證是最常用的一個(gè)認(rèn)證方式。
BasicAuthenticationFilter 認(rèn)證filter,經(jīng)過(guò)這些過(guò)濾器后SecurityContextHolder中將包含一個(gè)完全組裝好的Authentication對(duì)象,從而使后續(xù)鑒權(quán)能正常執(zhí)行
SecurityContextHolderAwareRequestFilter 此過(guò)濾器對(duì)ServletRequest進(jìn)行了一次包裝,使得request具有更加豐富的API
JaasApiIntegrationFilter (JAAS)認(rèn)證方式filter
RememberMeAuthenticationFilter 記憶認(rèn)證處理過(guò)濾器,即是如果前面認(rèn)證過(guò)濾器沒(méi)有對(duì)當(dāng)前的請(qǐng)求進(jìn)行處理,啟用了RememberMe功能,會(huì)從cookie中解析出用戶,并進(jìn)行認(rèn)證處理,之后在SecurityContextHolder中存入一個(gè)Authentication對(duì)象。
AnonymousAuthenticationFilter 匿名認(rèn)證處理過(guò)濾器,當(dāng)SecurityContextHolder中認(rèn)證信息為空,則會(huì)創(chuàng)建一個(gè)匿名用戶存入到SecurityContextHolder中
SessionManagementFilter 會(huì)話管理Filter,持久化用戶登錄信息,可以保存到session中,也可以保存到cookie或者redis中
ExceptionTranslationFilter 異常處理過(guò)濾器,主要攔截后續(xù)過(guò)濾器(FilterSecurityInterceptor)操作中拋出的異常。
FilterSecurityInterceptor 安全攔截過(guò)濾器類,獲取當(dāng)前請(qǐng)求url對(duì)應(yīng)的ConfigAttribute,并調(diào)用accessDecisionManager進(jìn)行訪問(wèn)授權(quán)決策。

spring security的默認(rèn)filter鏈:

 SecurityContextPersistenceFilter
->HeaderWriterFilter
->LogoutFilter
->UsernamePasswordAuthenticationFilter
->RequestCacheAwareFilter
->SecurityContextHolderAwareRequestFilter
->SessionManagementFilter
->ExceptionTranslationFilter
->FilterSecurityInterceptor

在上節(jié)我們已分析了核心的filter源碼以及功能??苫乜瓷瞎?jié)源碼分析更加深入的了解各個(gè)filter工作原理。

總結(jié):

??在認(rèn)證和訪問(wèn)授權(quán)過(guò)程前,首先必須進(jìn)行WebSecurityConfigurer符合自身應(yīng)用的security Configurer,也要清楚filter鏈的先后順序,才能更好理解spring security的工作原理以及在項(xiàng)目中出現(xiàn)的問(wèn)題定位。了解完準(zhǔn)備工作,接下來(lái)將展開(kāi)對(duì)認(rèn)證和訪問(wèn)授權(quán)模塊的工作流程研究以及項(xiàng)目示例分析。最后如有錯(cuò)誤可評(píng)論告知。

最后可關(guān)注公眾號(hào),一起學(xué)習(xí)。加群,每天會(huì)分享干貨,還有學(xué)習(xí)視頻領(lǐng)?。?/p>

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容