Spring Security源碼閱讀3-Spring Security過濾器鏈的初始化2

Spring Security源碼閱讀2-Spring Security過濾器鏈的初始化1文章中,遺留了如下兩個問題:

  1. 在步驟(15)中,我們說HttpSecurity類中的performBuild函數返回了DefaultSecurityFilterChain類,而該類中封裝了該鏈的所有過濾器,那麼這些過濾器是如何添加進來的呢?

  2. 在步驟(17)中生成的FilterChainProxy對象也是Filter實例,那麼在收到客戶端請求後是如何完成過濾器鏈式調用的呢?

接下來我們就逐步回答上面兩個問題。本文先回答問題2,因爲回答問題2後,讀者就可以從全視角瞭解Spring Security過濾器的創建和調用流程。本文中會將該問題拆的更細,按由簡到繁的順序來解答。

1. FilterChainProxy如何實現過濾器鏈調用

根據Filter接口的定義,可以確定每次客戶端請求在經過過濾器時調用的都是doFilter函數,FilterChainProxy對doFilter函數的實現如下:

@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
		if (clearContext) {
			try {
				request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
				(1)doFilterInternal(request, response, chain);
			}
			finally {
				SecurityContextHolder.clearContext();
				request.removeAttribute(FILTER_APPLIED);
			}
		}
		else {
			doFilterInternal(request, response, chain);
		}
	}

(1) 派發到過濾器鏈上執行,可以看到doFilter調用的是FilterChainProxy實例的內部函數doFilterInternal,其代碼如下:

private void doFilterInternal(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

   FirewalledRequest fwRequest = firewall
         .getFirewalledRequest((HttpServletRequest) request);
   HttpServletResponse fwResponse = firewall
         .getFirewalledResponse((HttpServletResponse) response);

   (2)List<Filter> filters = getFilters(fwRequest);

   if (filters == null || filters.size() == 0) {
      if (logger.isDebugEnabled()) {
         logger.debug(UrlUtils.buildRequestUrl(fwRequest)
               + (filters == null ? " has no matching filters"
                     : " has an empty filter list"));
      }

      fwRequest.reset();

      (3)chain.doFilter(fwRequest, fwResponse);

      return;
   }

   (4)VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
   (5)vfc.doFilter(fwRequest, fwResponse);
}

(2) 根據每個過濾器鏈配置的RequestMathcher,決定每一個請求要經過哪些過濾器。(參考Spring Security源碼閱讀2-Spring Security過濾器鏈的初始化1第(15)步,HttpSecurity在返回performBuild中返回的是DefaultSecurityFilterChain類的實例,而在創造DefaultSecurityFilterChain實例時傳遞的RequestMatcher實例是AnyRequestMatcher.INSTANCE,其matches函數默認返回true,請參考附錄代碼一)。

(3) 如果當前過濾器鏈沒有匹配的過濾器,則執行下一條過濾器鏈。

(4) 將所有的過濾器合併成一個虛擬過濾器鏈。

(5) 執行虛擬過濾器鏈。

可以看到最終執行的是虛擬過濾器鏈VirtualFilterChain類的實例,接下來看看VirtualFilterChain類doFilter的實現:

@Override
		public void doFilter(ServletRequest request, ServletResponse response)
				throws IOException, ServletException {
			if (currentPosition == size) {
				if (logger.isDebugEnabled()) {
					logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
							+ " reached end of additional filter chain; proceeding with original chain");
				}

				// Deactivate path stripping as we exit the security filter chain
				this.firewalledRequest.reset();

				(6)originalChain.doFilter(request, response);
			}
			else {
				currentPosition++;

				(7)Filter nextFilter = additionalFilters.get(currentPosition - 1);

				if (logger.isDebugEnabled()) {
					logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
							+ " at position " + currentPosition + " of " + size
							+ " in additional filter chain; firing Filter: '"
							+ nextFilter.getClass().getSimpleName() + "'");
				}

				(8)nextFilter.doFilter(request, response, this);
			}
		}
	}

(6) 如果當前虛擬過濾器鏈上的所有過濾器都已經執行完畢,則執行原生過濾器鏈上的剩餘邏輯。

(7) 獲得當前虛擬過濾器鏈上的下一個過濾器。

(8) 執行過濾器。doFilter的函數原型定義如下:

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

我們可以看到第三個參數傳遞的是this,即當前虛擬過濾器鏈實例。因此,當在nextFilter的doFilter函數中再次通過chain參數調用doFilter函數時,則會再次調用到當前虛擬過濾器鏈實例(僞代碼如下),從而完成虛擬過濾器鏈上的所有過濾器的調用 。

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
   ......
   chain.doFilter(request, response, chain); //這裏的chain是傳進來的VirtualFilterChain實例
   ......
}

以上就是FilterChainProxy實現過濾器鏈調用的全過程。

接下來討論一下客戶端請求是如何傳遞到過濾器的,但是要了解請求傳遞到過濾器的過程,就必須得清楚過濾器在Servlet容器中註冊的是什麼。

2. Spring Security過濾器如何註冊到Servlet容器

這裏先說明一下,爲什麼本節的標題用的是過濾器而非過濾器鏈,因爲本文第一節已經分析了FilterChainProxy實現過濾器鏈調用的原理,而FilterChainProxy本質上也是一個Filter實例。

在IDE中啓動Spring應用默認使用的是Spring Boot內嵌的Tomcat容器,因此這裏討論的Servlet容器指的是Tomcat的Servlet容器。在啓動Spring應用時會向IoC註冊securityFilterChainRegistration Bean:

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {

	private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

	@Bean
	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
			SecurityProperties securityProperties) {
		(9)DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
				DEFAULT_FILTER_NAME);
		registration.setOrder(securityProperties.getFilter().getOrder());
		registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
		return registration;
	}
	......
}

(9) DelegatingFilterProxyRegistrationBean實現了ServletContextInitializer接口,該接口用於配置Servlet上下文。DelegatingFilterProxyRegistrationBean目的是向Servlet容器中註冊一個過濾器:實現類爲 DelegatingFilterProxy 的一個 Servlet Filter。DelegatingFilterProxy 其實是一個代理過濾器,它被 Servlet 容器用於匹配特定URL模式的請求,而它會將任務委託給Spring管理的Bean,即名字爲 springSecurityFilterChain 的Bean,而springSecurityFilterChain正是Spring Security源碼閱讀2-Spring Security過濾器鏈的初始化1中介紹的,從而實現了Servlet容器管理的DelegatingFilterProxy與Spring容器創建的springSecurityFilterChain Bean的關聯。關於這一段結論的代碼分析,請參考附錄代碼二。

爲了加深對DelegatingFilterProxy的理解,我們可以看下其註釋:

/*Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that
 * implements the Filter interface. Supports a "targetBeanName" filter init-param
 * in {@code web.xml}, specifying the name of the target bean in the Spring
 * application context.
 */

清楚了Servlet容器管理的DelegatingFilterProxy與Spring容器創建的springSecurityFilterChain Bean的關聯,接下來就可以分析客戶端請求如何傳遞到過濾器。

3. 客戶端請求如何傳遞到過濾器

當用戶請求到來並且與過濾器的URL模式匹配後,會調用DelegatingFilterProxy的doFilter函數:

  @Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// Lazily initialize the delegate if necessary.
		Filter delegateToUse = this.delegate;
		if (delegateToUse == null) {
			synchronized (this.delegateMonitor) {
				delegateToUse = this.delegate;
				if (delegateToUse == null) {
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: " +
								"no ContextLoaderListener or DispatcherServlet registered?");
					}
					(10)delegateToUse = initDelegate(wac);
				}
				this.delegate = delegateToUse;
			}
		}

		// Let the delegate perform the actual doFilter operation.
		(11)invokeDelegate(delegateToUse, request, response, filterChain);
	}

(10) 獲得Spring容器管理的springSecurityFilterChain Bean.

(11) 調用FilterChainProxy的doFilter函數,代碼如下:

protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		delegate.doFilter(request, response, filterChain);
	}

以上就是Spring Security過濾器的調用和執行流程。在整個分析中,爲了分析的簡單和文章不至於太長,我並沒有詳細分析Servlet容器和Spring IoC容器的交互過程,有興趣的讀者可以自行分析。

附錄

1. 代碼一

HttpSecurity.java

	private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
 
  @Override
	protected DefaultSecurityFilterChain performBuild() {
		filters.sort(comparator);
		return new DefaultSecurityFilterChain(requestMatcher, filters);
	}

2. 代碼二

我們直接從ServletWebServerApplicationContext類開始分析,因爲在Servlet類型應用中,實際實例化的應用上下文爲ServletWebServerApplicationContext。爲什麼會如此,讀者可以分析一下Spring Boot的啓動流程。

   /**
	 * Returns the {@link ServletContextInitializer} that will be used to complete the
	 * setup of this {@link WebApplicationContext}.
	 * @return the self initializer
	 * @see #prepareWebApplicationContext(ServletContext)
	 */
	private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return this::selfInitialize;
	}

	private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		(1)for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

(1) 回調所有ServletContextInitializer的onStart函數。通過調試,ServletWebServerApplicationContext中有如下這些ServletContextInitializer:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-N2m2sgf5-1582177602093)(/Users/liulijun/projects/blog/source/_posts/Spring-Security-Source-Code2-filter-Chain2/ServletContextInitializer.png)]

可以看到DelegatingFilterProxyRegistrationBean類的實例,再看看其onStart回調函數,其最終回調的是addRegistration函數(DelegatingFilterProxyRegistrationBean繼承自AbstractFilterRegistrationBean,該函數位於其中):

  @Override
	protected Dynamic addRegistration(String description, ServletContext servletContext) {
		Filter filter = getFilter();
		(2)return servletContext.addFilter(getOrDeduceName(filter), filter);
	}

(2) 通過調用DelegatingFilterProxyRegistrationBean中的getFilter函數獲得DelegatingFilterProxy類的實例,並將基添加到Servlet上下文中,最終將過濾器添加到StandardContext(這個就是Tomcat的上下文了,從而建立了Tomcat容器與Spring的關係)的filterDefs屬性中。

Github博客地址
知乎

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