Spring Security 過濾器註冊脈絡梳理

1 簡述

Spring Security 本質上就是通過一系列的過濾器,進行業務的處理。

Spring Security 在 Servlet 的過濾鏈(filter chain)中註冊瞭一個過濾器 FilterChainProxy,它會把請求代理到 Spring Security 自己維護的多個過濾鏈,每個過濾鏈會匹配一些 URL,如果匹配則執行對應的過濾器。過濾鏈是有順序的,一個請求隻會執行第一條匹配的過濾鏈。Spring Security 的配置本質上就是新增、刪除、修改過濾器

但是萬物終歸有源頭,過濾器是如何註冊進來的,通過過程瞭解註冊的骨架。

註明 這裡隻是使用瞭 spring web + spring security,同時使用 web.xml 的風格進行配置,如果你使用 SPI 機制(沒有使用 web.xml),殊途同歸。

2 註冊過程

2.1 web.xml 配置

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

其中 DelegatingFilterProxy 類圖如下:

根據類圖看出,DelegatingFilterProxy 繼承 GenericFilterBean,其間接實現瞭 Filter 接口,所以從類型上看,其也是一個過濾器。

 從 Spring 容器中尋找 targetBeanName=springSecurityFilterChain 的 Bean, 從 DelegatingFilterProxy 的名字上看,知道它其實是一個代理類,真正執行業務的,是 filter-name 指定的 springSecurityFilterChain 這個 bean。接下來,問題來瞭,springSecurityFilterChain 這個 bean 又是在什麼時候註冊的呢

2.2 EnableWebSecurity 註解

我們一般在使用 Spring Security 的時候,都會自定義繼承 WebSecurityConfigurerAdapter,同時結合使用 EnableWebSecurity 註解,然後在自定義的類中進行各種各樣滿足業務的工作。

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    ...
}

這裡我們重點關註的是 EnableWebSecurity 註解,查看下其源碼,做瞭兩個非常重要的點

  • 1 導入 WebSecurityConfiguration 配置。
  • 2 通過 @EnableGlobalAuthentication 註解引入全局配置
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ 
    WebSecurityConfiguration.class,
    SpringWebMvcImportSelector.class,
    OAuth2ImportSelector.class,
    HttpSecurityConfiguration.class
})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

   /**
    * Controls debugging support for Spring Security. Default is false.
    * @return if true, enables debug support with Spring Security
    */
   boolean debug() default false;

}

此註解中由使用瞭 Import 註解引入瞭其他的 bean,然後查看其源碼,重點關註 WebSecurityConfiguration

2.3 WebSecurityConfiguration 類

這個類裡面比較重要的就兩個方法:

1 springSecurityFilterChain

springSecurityFilterChain 方法上添加瞭 @Bean註解,可以知道是創建瞭springSecurityFilterChain bean

2 setFilterChainProxySecurityConfigurer

這個方式設置瞭對應的配置,註意,這個方法優先上面的方法執行。

分析其重點代碼

private WebSecurity webSecurity;

// 註入 bean
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
   boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
   boolean hasFilterChain = !this.securityFilterChains.isEmpty();
   ...
   if (!hasConfigurers && !hasFilterChain) {
      WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
            .postProcess(new WebSecurityConfigurerAdapter() {
            });
      this.webSecurity.apply(adapter);
   }
   for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
      this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
      for (Filter filter : securityFilterChain.getFilters()) {
         if (filter instanceof FilterSecurityInterceptor) {
            this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
            break;
         }
      }
   }
   for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
      customizer.customize(this.webSecurity);
   }

   // 重點關註
   return this.webSecurity.build();
}
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
      @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
      throws Exception {
   this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
   if (this.debugEnabled != null) {
      this.webSecurity.debug(this.debugEnabled);
   }
   webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
   Integer previousOrder = null;
   Object previousConfig = null;
   for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
      Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
      if (previousOrder != null && previousOrder.equals(order)) {
         throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order
               + " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
      }
      previousOrder = order;
      previousConfig = config;
   }
   for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
      this.webSecurity.apply(webSecurityConfigurer);
   }
   this.webSecurityConfigurers = webSecurityConfigurers;
}

我們先看執行的 setFilterChainProxySecurityConfigurer 方法,其中參數 webSecurityConfigurers 是一個 List,它實際上是所有 WebSecurityConfigurerAdapter 的子類,那如果我們定義瞭自定義的配置類,也意味著讀取瞭我們自定義的類。

接著看到 springSecurityFilterChain 方法註冊瞭一個名字為 AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME 的 bean,翻看源碼,其實名字就是在 web.xml 中配置的 filter-name,即 springSecurityFilterChain,到這裡困惑解開瞭一部分,原來查詢的 bean 是在這裡註入的。同時也要求瞭在使用 spring-security 時,在 web.xml 中配置 filter 時,不能是其他名字。

根據 this.webSecurity.build 這行代碼,發現真正構建過濾器的是 WebSecurity 類

2.4 WebSecurity 類

接上一步中的代碼

   // 重點關註
   return this.webSecurity.build();

知道起作用的是 WebSecurity 類,其類圖結構如下:

根據源碼,定位到 WebSecurity 類中的 performBuild 方法

@Override
protected Filter performBuild() throws Exception {
   ...
   int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
   List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
   for (RequestMatcher ignoredRequest : this.ignoredRequests) {
      securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
   }

   // ① 調用 securityFilterChainBuilder 的 build() 方法
   for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
      securityFilterChains.add(securityFilterChainBuilder.build());
   }
   FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
   if (this.httpFirewall != null) {
      filterChainProxy.setFirewall(this.httpFirewall);
   }
   if (this.requestRejectedHandler != null) {
      filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
   }
   filterChainProxy.afterPropertiesSet();

   Filter result = filterChainProxy;
   ...
   this.postBuildAction.run();
   return result;
}

代碼分析:

  • 1 代碼中的  處,重點關註,通過斷點調試,發現其實這裡的 securityFilterChainBuilder 就是 HttpSecurity,到這裡也很清楚瞭,默認以及自定義過濾器是通過 HttpSecurity 加載進來的。
  • 2 創建好過濾器鏈之後,然後實例化 filterChainProxy 進行返回

根據源碼知道 performBuild 方法最終返回的其實是一個 filterChainProxy 實例,接下來我們再關註下 filterChainProxy 類。

2.5 FilterChainProxy 類

其類圖結構如下

查看其關鍵代碼:

public class FilterChainProxy extends GenericFilterBean {

   // 維護的 spring security 過濾器鏈列表
   private List<SecurityFilterChain> filterChains;
   public FilterChainProxy(SecurityFilterChain chain) {
      this(Arrays.asList(chain));
   }
   public FilterChainProxy(List<SecurityFilterChain> filterChains) {
      this.filterChains = filterChains;
   }
    @Override
    public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
            boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
            if (!clearContext) {
                    doFilterInternal(request, response, chain);
                    return;
            }
            try {
                    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

                    // 真正起作用的函數
                    doFilterInternal(request, response, chain);
            }
            catch (RequestRejectedException ex) {
                    this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
            }
    ...
    }
    private void doFilterInternal(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
            FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest(
                (HttpServletRequest) request);
            HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse(
                (HttpServletResponse) response);
            List<Filter> filters = getFilters(firewallRequest);
            if (filters == null || filters.size() == 0) {
                    ...
                    chain.doFilter(firewallRequest, firewallResponse);
                    return;
            }
            VirtualFilterChain virtualFilterChain = new VirtualFilterChain(
                firewallRequest, chain, filters);
            virtualFilterChain.doFilter(firewallRequest, firewallResponse);
    }
}

通過代碼分析,FilterChainProxy 這個類維護瞭真正的過濾器鏈列表,即 SecurityFilterChain 類型的過濾器鏈,註意,SecurityFilterChain 是過濾器鏈,而不是一個個的過濾器,過濾器會有其他的操作塞到過濾器鏈中,即 2.4 中的代碼塊,代碼如下。

   for (RequestMatcher ignoredRequest : this.ignoredRequests) {
      securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
   }
   // ① 調用 securityFilterChainBuilder 的 build() 方法
   for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
      securityFilterChains.add(securityFilterChainBuilder.build());
   }

這段代碼其實執行的業務就是把過濾器塞入到過濾器鏈中。

這裡也更加清楚瞭, Spring Security Filter 並不是直接嵌入到 Web Filter 中的,而是通過 FilterChainProxy 來統一管理 Spring Security FilterFilterChainProxy 本身則通過 Spring 提供的 DelegatingFilterProxy 代理過濾器嵌入到 Web Filter 之中。

3 小結

根據趟源碼,大概瞭解瞭過濾器註冊的流程:

  • web.xml 中 DelegatingFilterProxy 配置
  • EnableWebSecurity 註解引入配置初始化
  • WebSecurityConfiguration 類註入 springSecurityFilterChain 的 bean 並開始構建過濾器
  • WebSecurity 類中的 performBuild 方法把過濾器都處理好,放到過濾器鏈中,接著實例化 filterChainProxy,這裡實例化返回的對象就是第三步的過濾器

到此這篇關於Spring Security 過濾器註冊脈絡梳理的文章就介紹到這瞭,更多相關Spring Security 內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: