SpringSecurity原理(四)——過濾器

概述

SpringSecurity原理(一)——初探 SpringSecurity原理(二)——認證 SpringSecurity原理(三)——授權 SpringSecurity原理(四)——過濾器 SpringSecurity原理(五)——擴展與配置

前面的文章中我們已經基本瞭解了怎樣使用Spring Security的基本的認證(Authentication)和授權(Authority)

但是,我們還不清楚Spring Security到底是怎樣執行這些流程。

這篇文章,我們就以SpringBoot爲例,來梳理一下Spring Security從啓動到執行這些流程的過程。

前置知識

我們知道Spring Security是通過Filter的方式來完成它的核心流程。但是:

  1. Spring Security到底擁有哪些Filter?
  2. 這些Filter是如何注入容器?
  3. 我們如何自定義自己的Filter?

web.xml配置

前面我們已經介紹過了,最開始如果我們要配置Filter,一般是通過web.xml的方式:

<filter>  
   <filter-name>deleFilter</filter-name>  
   <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
   <init-param>  
      <param-name>targetBeanName</param-name>  
      <param-value>spring-bean-name</param-value>  
   </init-param>  
</filter>          
<filter-mapping>  
   <filter-name>deleFilter</filter-name>  
   <url-pattern>/*</url-pattern>  
</filter-mapping>  

SpringBoot中添加Filter

在SpringBoot中可以通過@WebFilter和@ServletComponentScan註解,注入自定義的Filter。

@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    }

    @Override
    public void destroy() {
    }
}

@SpringBootApplication
@ServletComponentScan(basePackages = "vip.mycollege.filter")
public class StartApplication {

    public static void main(String[] args) {
        SpringApplication.run(StartApplication.class, args);
    }
}

也可以通過FilterRegistrationBean的方式,注入自定義的Filter。

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new MyFilter());
        bean.addUrlPatterns("/*");
        return bean;
    }
}

也可以通過DelegatingFilterProxyRegistrationBean的方式。

@Configuration
public class FilterConfig {
    @Bean("proxyFilter")
    public Filter filter (){
        return new Filter() {
            @Override
            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
            }

            @Override
            public void destroy() {
            }
        
    @Bean
    public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean(){
        DelegatingFilterProxyRegistrationBean bean = new DelegatingFilterProxyRegistrationBean("proxyFilter");
        bean.addUrlPatterns("/*");
        return bean;
    }
}

DelegatingFilterProxyRegistrationBean和FilterRegistrationBean都繼承了AbstractFilterRegistrationBean,從名字上看就知道是一個RegistrationBean,也就是說會在Servlet容器啓動的時候被注入。

DelegatingFilterProxyRegistrationBean會在Servlet容器中註冊一個DelegatingFilterProxy,用來代理Spring IoC容器中某個指定名稱的Filter bean。

FilterChainProxy

SpringBoot有一個SecurityFilterAutoConfiguration的自動配置類,就會配置一個name爲springSecurityFilterChain的DelegatingFilterProxyRegistrationBean,這個類的url-pattern默認爲/*,也就是說會過濾所有的請求。

name是springSecurityFilterChain是一個什麼鬼呢?

答案是:FilterChainProxy。

這個類是在HttpSecurityBeanDefinitionParser的registerFilterChainProxyIfNecessary方法中註冊。

HttpSecurityBeanDefinitionParser也是一個BeanDefinitionParser,因此它會通過parse方法來構建Filter類。

整個流程現在就清晰了:

  1. SpringBoot通過自動配置類搞了個DelegatingFilterProxyRegistrationBean
  2. DelegatingFilterProxyRegistrationBean會在Servlet啓動的時候註冊一個DelegatingFilterProxy
  3. DelegatingFilterProxy會默認會攔截所有的請求,然後交個一個別名爲springSecurityFilterChain的FilterChainProxy
  4. FilterChainProxy在持有一個SecurityFilterChain的list
  5. SecurityFilterChain本身又持有一個Filter列表,可以通過match找出url匹配的Request交個filters處理

FilterChainProxy除了持有過濾器,默認內置了一個StrictHttpFirewall一個HTTP防火牆,它採用了嚴格模式,遇到任何可疑的請求,會通過拋出異常RequestRejectedException拒絕該請求。

現在我們知道了Spring Security如何收集利用Filter了。

但是,Spring Security到底揹着我們弄了哪些Filter呢?

我只想說很多,要知道有哪些也很簡單,在FilterChainProxy打一個斷點,debug,看一下filterChains變量中的filters列表就能看到有哪些filter

默認情況下filterChains只有一個filte,就是DefaultSecurityFilterChain,看名字就知道這是一個SecurityFilterChain,他包含了一個Filter列表,默認有:

  1. WebAsyncManagerIntegrationFilter:與處理異步請求映射的 WebAsyncManager 進行集成
  2. SecurityContextPersistenceFilter: 請求前保存和請求後清除SecurityContextHolder中的安全上下文
  3. HeaderWriterFilter:將頭信息加入響應中
  4. CsrfFilter:處理跨站請求僞造
  5. LogoutFilter:處理登出
  6. UsernamePasswordAuthenticationFilter:處理基於表單的登錄
  7. DefaultLoginPageGeneratingFilter:如果沒有配置登錄頁,生成默認登錄頁
  8. DefaultLogoutPageGeneratingFilter:如果沒有登出頁,生成默認登出頁
  9. BasicAuthenticationFilter:處理HTTP BASIC認證
  10. RequestCacheAwareFilter:處理請求的緩存
  11. SecurityContextHolderAwareRequestFilter:包裝請求對象request
  12. AnonymousAuthenticationFilter:檢測SecurityContextHolder是否存在Authentication,如不存在提供一個匿名 Authentication
  13. SessionManagementFilter:管理 session 的過濾器
  14. ExceptionTranslationFilter:處理 AccessDeniedException 和 AuthenticationException 異常
  15. FilterSecurityInterceptor: 權限校驗相關

重要Filter

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter本身沒啥好說的,它就是一個Filter,但是因爲它用得多,所以說一下。

Filter肯定先看doFilter方法,UsernamePasswordAuthenticationFilter的主要認證邏輯在attemptAuthentication:

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException {
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    String username = obtainUsername(request);
    username = (username != null) ? username : "";
    username = username.trim();
    String password = obtainPassword(request);
    password = (password != null) ? password : "";
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
    setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest);
}

很簡單,就是從request中獲取username和password的字段,封裝成UsernamePasswordAuthenticationToken,然後扔給AuthenticationManager去執行認證,當然,最終認證邏輯肯定是像DaoAuthenticationProvider 這樣的AuthenticationProvider執行。

FilterSecurityInterceptor

FilterSecurityInterceptor主要是用來做權限校驗的,具體的鑑權邏輯主要在AbstractSecurityInterceptor中。

FilterSecurityInterceptor也是一個Filter,所以,還是先看doFilter方法,調用了invoke:

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
        //一次請求中避免重複檢查
		if (isApplied(filterInvocation) && this.observeOncePerRequest) {
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
			return;
		}
		// 第一次調用,先設置標記,避免重複調用
		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
        // 業務邏輯調用之前,執行檢查鑑權操作主要就是在這裏面完成
		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
		try {
            // 執行具體的業務邏輯
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		}
		finally {
			super.finallyInvocation(token);
		}
        // 業務邏輯調用之後,主要是處理返回結果
		super.afterInvocation(token, null);
	}

FilterInvocation就是FilterInvocation、ServletResponse、FilterChain的簡單封裝。

我們看到整個invoke的邏輯非常清晰,很像AOP的around結構。

ExceptionTranslationFilter

ExceptionTranslationFilter的邏輯有點奇怪,它主要是爲了處理 AccessDeniedException 和 AuthenticationException 異常。但是並不是處理它前面產生的異常,而是它後面的Filter產生的異常,因爲它前面Filter如果異常了根本到不了它這裏。

它後面,默認就只有FilterSecurityInterceptor了,主要會產生AccessDeniedException授權異常,AuthenticationException是因爲有一個再認證的過程。

過濾器

  • WebAsyncManagerIntegrationFilter
  • SecurityContextPersistenceFilter
  • ChannelProcessingFilter
  • ConcurrentSessionFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • ConcurrentSessionFilter
  • OpenIDAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • SwitchUserFilter
  • FilterSecurityInterceptor

文檔

Spring Security官方文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章