Spring Security Java Config 淺析

在之前的項目中使用接觸過通過 xml 配置使用 Spring Security。但是最近比較流行 Java Config來配置使用 ``Spring Security。並且對它的 builder 模式比較感興趣,就來查看了它的源碼實現。下面就把我自己的分析結果記錄下來,希望對閱讀這篇博客的你也有所幫助。在 Spring Security 裏面有兩個非常重要的概念認證與授權。

  • 認證:是確認某主體在某系統中是否合法、可用的過程。這裏的主體既可以是登陸系統的用戶也可以是接入 的設備或者其他系統
  • 授權:是指當前主體通過認證之後,是否允許其執行某項操作的過程

首先我們來認識一下 Spring Security 通過 Java 配置中的幾個重要的類。(基於 Spring Security 5.2.2-RELEASE)

1、 Spring Security 配置中幾個重要的類

下面以 Spring Security 中幾個重要的類給大家簡單的介紹一下它們的功能。不然在後面的源碼講解當中大家可能會迷失到 Spring Security 的類中。

1.1 FilterChainProxy

FilterChainProxy : Spring Security 實現原理是通過遍歷它的內部對象 SecurityFilterChain 列表。它的內部有兩個方法一個是 SecurityFilterChain#match ,另一個就是獲取 Java Servlet Api 的中 Filter 鏈表的SecurityFilterChain#getFilters。當有外部請求來的時候,通過 match 方法來匹配請求參數 HttpServletRequest 。如果這個 SecurityFilterChain 匹配請求HttpServletRequest 。那麼就通過 SecurityFi`lterChain#getFilters 獲取當 Filter 列表來對請求實現認證與授權。而 FilterChainProxy 是通過 WebSecurityConfiguration#springSecurityFilterChain 進行創建的。

1.2 WebSecurityConfiguration

WebSecurityConfiguration : WebSecurityConfiguration 用於初始化 WebSecurity 配置。首先,在 WebSecurityConfiguration#setFilterChainProxySecurityConfigurer 方法中,它以配置 Spring Security 時候繼承自 WebSecurityConfigurerAdapter 的配置類來初始化一個 SecurityConfigurer 列表, Spring Security 以 SecurityConfigurer 列表爲依據,啓用所需的安全策略。

它通過 @EnableWebSecurity 激活,並且@EnableWebSecurity 還有一個 debug 參數用於指定是否採用調式模式,默認是 false。在調式模式下,請個請求的詳細信息和所經過的過濾器,甚至其實調用棧都會被打印到控制檯。

1.3 HttpSecurity

HttpSecurity : HttpSecurity 就是 Spring Security Xml 配置對應 http 命名空間中元素。它允許爲特定的http配置基於web的安全性請求。默認情況下,它將應用於所有請求,但可以使用 requestMatcher(requestMatcher) 或其他類似的方法進行限制。

下面可以看到最基本的基於表單的配置。配置將要求任何被請求的URL都需要一個角色爲“ROLE_USER”的用戶。它還定義了一個具有用戶名的內存中的身份驗證方案user、密碼password和角色ROLE_USER。更多的例子,在HttpSecurity中引用各個方法的Java文檔。

 @Configuration
 @EnableWebSecurity
 public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
 
 @Override
 protected void configure(HttpSecurity http) throws Exception {
  		http.authorizeRequests().antMatchers("/**).hasRole(“/USER”).and().formLogin();
  	}
 
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  		auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
  	}
}

1.4 WebSecurity

WebSecurityWebSecurityConfiguration#springSecurityFilterChain 中通過 WebSecurity#build 來創建 FilterChainProxy。也就是 Spring Security 的攔截器 Filter 鏈。

WebSecurity 實現了 SecurityBuilder 並通過 WebSecurity#performBuild 方法創建 FilterChainProxy
WebSecurity 的父類 AbstractConfiguredSecurityBuilder#doBuild 中,它的AbstractConfiguredSecurityBuilder#init 以及 AbstractConfiguredSecurityBuilder#configure 會獲取 SecurityConfigurer 來通過調用 SecurityConfigurer#initSecurityConfigurer#configureSecurityBuilder對象進行處理。

其實WebSecurity#performBuild是通過SecurityBuilder#build這個方法觸發的。Spring Security 通過 Java 配置來配置 SecurityBuilderSecurityConfigurer 是非常重要的兩個接口。

1.5 SecurityBuilder

SecurityBuilder是 Spring Security 定義用來 Spring Security 的對象創建接口。它只有一個接口 build,它的核心實現類就是我們上面提到的 HttpSecurityWebSecurity

public interface SecurityBuilder<O> {

	/**
	 * Builds the object and returns it or null.
	 *
	 * @return the Object to be built or null if the implementation allows it.
	 * @throws Exception if an error occurred when building the Object
	 */
	O build() throws Exception;
}

1.6 SecurityConfigurer

SecurityConfigurer接口定義了兩個方法 SecurityConfigurer#initSecurityConfigurer#configureSecurityConfigurer接口的主要作用是用來配置 SecurityBuilder.所有的 SecurityConfigurer,首先調用 #init(SecurityBuilder) ,然後調用 #configure(SecurityBuilder)SecurityBuilder 進行配置。

SecurityConfigurer.java

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
	/**
	 * Initialize the {@link SecurityBuilder}. Here only shared state should be created
	 * and modified, but not properties on the {@link SecurityBuilder} used for building
	 * the object. This ensures that the {@link #configure(SecurityBuilder)} method uses
	 * the correct shared objects when building. Configurers should be applied here.
	 *
	 * @param builder
	 * @throws Exception
	 */
	void init(B builder) throws Exception;

	/**
	 * Configure the {@link SecurityBuilder} by setting the necessary properties on the
	 * {@link SecurityBuilder}.
	 *
	 * @param builder
	 * @throws Exception
	 */
	void configure(B builder) throws Exception;
}

2、@EnanbleWebSecurity

@EnanbleWebSecurity 是開啓 Spring Security 的默認行爲,這它通過 @Import註解導入了WebSecurityConfiguration。也就是說 WebSecurityConfiguration 通過 @EnanbleWebSecurity 得到初始化的機會。

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class,
		OAuth2ImportSelector.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;
}

@EnanbleWebSecurity 可以通過 debug 參數用於指定是否採用調度,默認爲 false。在調度模式下,每個請求的詳細信息和所經過的過濾器,甚至其調用棧都會被打印到控制檯。

在這裏插入圖片描述
當你發送一個 http 請求時,控制檯會打印請求信息以及當前請求 uri 匹配 Spring Security 中的哪些過濾器列表。

3、WebSecurityConfiguration

WebSecurityConfiguration是於初始化 WebSecurity 配置類。首先通過 WebSecurityConfiguration#setFilterChainProxySecurityConfigurer 初始化 WebSecurity 對象。

	@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception {
		webSecurity = objectPostProcessor
				.postProcess(new WebSecurity(objectPostProcessor));
		if (debugEnabled != null) {
			webSecurity.debug(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) {
			webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}

以上代碼包含兩個步驟:

  • 首先創建 WebSecurity 對象實例,然後通過 objectPostProcessor 依賴注入需要注入的對象。就是 Spring 的 DI 機制。
  • 然後把 Spring 容器中的 SecurityConfigurer 對象添加到 WebSecurity,這裏需要注意的一點是,當我們使用 @EnanbleWebSecurity 標註一個類時,這個類都會繼承自 WebSecurityConfigurerAdapter。而WebSecurityConfigurerAdapter實現了 SecurityConfigurer,所以這裏就是把我們的配置類添加到 WebSecurity對象裏面。並且最終會添加到 WebSecurity 父類 AbstractConfiguredSecurityBuilderAbstractConfiguredSecurityBuilder#configurers 屬性中(圈起來會考)。

然後通過 WebSecurityConfiguration#springSecurityFilterChain 來 初始化 Spring Security 裏面的最最關鍵的類 FilterChainProxy 這個過濾器鏈。

	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) {
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
		return webSecurity.build();
	}

FilterChainProxy內部有 FilterChainProxy#filterChains 屬性,也就是 SecurityFilterChain列表。

SecurityFilterChain.java

public interface SecurityFilterChain {

	boolean matches(HttpServletRequest request);

	List<Filter> getFilters();
}

當有 http 請求來臨的時候, SecurityFilterChain列表會判斷這個請求是否滿足條件,如果這個請求的滿足條件就會獲取當前SecurityFilterChain中的 javax.servlet.Filter 列表,過濾當前的請求。達到最終的認證授權效果。

4、FilterChainProxy 的創建過程

通過上面的講解可以知道 FilterChainProxy 的創建入口是 WebSecurityConfiguration#springSecurityFilterChain。而它是通過 WebSercurity#build 來創建FilterChainProxy 對象的。 這裏需要着重強調的一下就是 WebSercurity 實現最終實現了 SecurityBuilder 接口。可以回頭看一下第一小節中 Spring Security 裏面幾個重要的類。當進行創建 FilterChainProxy 的時候最後會調用到 AbstractConfiguredSecurityBuilder#doBuild 方法。

AbstractConfiguredSecurityBuilder#doBuild`

	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;

			beforeInit();
			init();

			buildState = BuildState.CONFIGURING;

			beforeConfigure();
			configure();

			buildState = BuildState.BUILDING;

			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
	}

下面我們就來分析一下這裏面的幾個方法:

  • AbstractConfiguredSecurityBuilder#beforeInit:調用SecurityConfigurer#init 方法之前的擴展方法,空實現。
  • AbstractConfiguredSecurityBuilder#init():初始化方法,會調用SecurityConfigurer#init。注意這裏的 SecurityConfigurer 對象其實就是我們標註了 @EnableWebSecurity 用來進行 Java 配置 Spring Security 的類。最終會調用到 WebSecurityConfigurerAdapter#init。首先它會獲取 HttpSecurity 並且通過 WebSecurity#addSecurityFilterChainBuilder添加到 WebSecurity 當中(構造 HttpSecurity 過程非常重要且複雜會在後期的章節介紹),然後把FilterSecurityInterceptor添加到 WebSecurity 當中。
  • AbstractConfiguredSecurityBuilder#beforeConfigure():調用SecurityConfigurer#configure 方法之前的擴展方法,空實現。
  • AbstractConfiguredSecurityBuilder#configure:調用標註@EnableWebSecurity 類的父類的 WebSecurityConfigurerAdapter#configure方法,空實現。
  • AbstractConfiguredSecurityBuilder#performBuild:會調用到它的子類也就是 WebSecurity#performBuild方法最終實例化 FilterChainProxy。如果@EnableWebSecurity註解中的debug() 方法標註爲 true,則會創建 org.springframework.security.web.debug.DebugFilter 顯示第 2 小節中的調度信息。

WebSecurity#performBuild

	protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
						+ "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
						+ "More advanced users can invoke "
						+ WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		if (debugEnabled) {
			logger.warn("\n\n"
					+ "********************************************************************\n"
					+ "**********        Security debugging is enabled.       *************\n"
					+ "**********    This may include sensitive information.  *************\n"
					+ "**********      Do not use in a production system!     *************\n"
					+ "********************************************************************\n\n");
			result = new DebugFilter(filterChainProxy);
		}
		postBuildAction.run();
		return result;
	}

看到了這裏大家就對 WebSecurity 這個類的定位非常的清晰,它其實就是一個橋樑的作用。把 HttpSecurity 這個配置類與 FilterChainProxy 這個真正的過濾 http 請求的類連接起來。當我們在使用 Spring Security 的 Java 配置的時候是通過 HttpSecurity 來進行配置的,然後 WebSecurity 把 HttpSecurity 設置到它的屬性當中。WebSecurity 本身是實現了 SecurityBuilder ,所以在WebSecurityConfiguration#springSecurityFilterChain當中通過 WebSercurity#build(也就是 SecurityBuilder 定義的 build 方法)來創建FilterChainProxy 對象的。

5、HttpSecurity 的創建過程

在第 4 章節中我們提到當 WebSecurity 創建FilterChainProxy 對象的過程中會調用AbstractConfiguredSecurityBuilder#init()方法。其實就是調用標註了@EnableWebSecurity 類的父類 WebSecurityConfigurerAdapter#init 方法。

WebSecurityConfigurerAdapter#init

	public void init(final WebSecurity web) throws Exception {
		final HttpSecurity http = getHttp();
		web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
			FilterSecurityInterceptor securityInterceptor = http
					.getSharedObject(FilterSecurityInterceptor.class);
			web.securityInterceptor(securityInterceptor);
		});
	}
	

裏面的核心方法其實就是 WebSecurityConfigurerAdapter#getHttp,下面我們就來具體分析一下這個方法:

WebSecurityConfigurerAdapter#getHttp

	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);
		authenticationBuilder.authenticationEventPublisher(eventPublisher);
		Map<Class<?>, Object> sharedObjects = createSharedObjects();

		http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
				sharedObjects);
		if (!disableDefaults) {
			// @formatter:off
			http
				.csrf().and()
				.addFilter(new WebAsyncManagerIntegrationFilter())
				.exceptionHandling().and()
				.headers().and()
				.sessionManagement().and()
				.securityContext().and()
				.requestCache().and()
				.anonymous().and()
				.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;
	}

這個方法看着挺複雜的我們就來理一下它的方法實現的具體功能:

  • 首先第一步創建 DefaultAuthenticationEventPublisher 對象用於發佈授權成功或者失敗事件。
  • 然後創建 AuthenticationManager 對象,它的作用主要是授權管理作用。
  • 接着就是創建並設置 HttpSecurity 的值。它默認會配置 11 個過濾器。
  • 最後通過 WebSecurityConfigurerAdapter#configure 對 HttpSecurity 進行配置。

HttpSecurity#csrf() 爲例,來講解 HttpSecurity 添加過濾器的過程:

HttpSecurity#csrf()

	public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
		ApplicationContext context = getContext();
		return getOrApply(new CsrfConfigurer<>(context));
	}
  • 首先拿到 Spring 框架的內部對象 ApplicationContext,也就是 bean 管理對象。
  • 接口創建 CsrfConfigurer 對象並調用 HttpSecurity#getOrApply 方法

HttpSecurity#getOrApply

	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
			C configurer) throws Exception {
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}
  • 首先判斷是否之前創建配置這個對象,如果有則直接返回
  • 然後調用 AbstractConfiguredSecurityBuilder#apply(C)進行配置

AbstractConfiguredSecurityBuilder#apply(C)

	public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
			throws Exception {
		configurer.addObjectPostProcessor(objectPostProcessor);
		configurer.setBuilder((B) this);
		add(configurer);
		return configurer;
	}
  • 爲當前 SecurityConfigurerAdapter 對象也就是CsrfConfigurer添加 ObjectPostProcessor 主要用於依賴注入操作
  • 設置當前 SecurityConfigurerAdapter#securityBuilder 的屬性是當前對象也就是 HttpSerurity 用於 HttpSecurity 的鏈式調用。通過 SecurityConfigurerAdapter#and 方法就可以獲取到 HttpSecurity 對象。
  • 最後通過AbstractConfiguredSecurityBuilder#add把 CsrfConfigurer 對象添加到 AbstractConfiguredSecurityBuilder#configurers

下面就是 HttpSecurity 默認添加的 11 個 Filter。

HttpSecurity中的方法 對應的 SecurityConfigurer 對應的 Filter
HttpSecurity#csrf() CsrfConfigurer CsrfFilter
HttpSecurity#addFilter ~ WebAsyncManagerIntegrationFilter
HttpSecurity#exceptionHandling ExceptionHandlingConfigurer ExceptionTranslationFilter
HttpSecurity#headers HeadersConfigurer HeaderWriterFilter
HttpSecurity#sessionManagement SessionManagementConfigurer SessionManagementFilter
HttpSecurity#securityContext SecurityContextConfigurer SecurityContextPersistenceFilter
HttpSecurity#requestCache RequestCacheConfigurer RequestCacheAwareFilter
HttpSecurity#anonymous AnonymousConfigurer AnonymousAuthenticationFilter
HttpSecurity#servletApi ServletApiConfigurer SecurityContextHolderAwareRequestFilter
HttpSecurity#apply DefaultLoginPageConfigurer DefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilter
HttpSecurity#ogout LogoutConfigurer LogoutFilter

在第二小節打印的大多過濾器都可以在上面找到。最後就是 WebSecurity 通過 WebSecurity#performBuild 方法創建FilterChainProxy

	protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
						+ "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
						+ "More advanced users can invoke "
						+ WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		if (debugEnabled) {
			logger.warn("\n\n"
					+ "********************************************************************\n"
					+ "**********        Security debugging is enabled.       *************\n"
					+ "**********    This may include sensitive information.  *************\n"
					+ "**********      Do not use in a production system!     *************\n"
					+ "********************************************************************\n\n");
			result = new DebugFilter(filterChainProxy);
		}
		postBuildAction.run();
		return result;
	}

這裏的代碼邏輯挺簡單的,裏面的 securityFilterChainBuilders 對象其實就是 HttpSecurity,通過HttpSecurity#build 方法創建 DefaultSecurityFilterChain

HttpSecurity#performBuild

	protected DefaultSecurityFilterChain performBuild() {
		filters.sort(comparator);
		return new DefaultSecurityFilterChain(requestMatcher, filters);
	}

然後再創建 FilterChainProxy 對象,如果@EnableWebSecurity#debug標註是 true,就會創建DebugFilter打印調度信息。

6、 Spring Security 的鏈式調用

Spring Security 的核心實現是通過一條過濾器鏈來確定用戶的每一個請求應該得到什麼樣的反饋,如下圖所示:
https://img-blog.csdn.net/20160623185249858

通過 WebSecurity 創建出來的 FilterChainProxy 其實間接的繼承了 Filter,可以作爲真正的過濾器使用。它會攜帶若干條過濾器鏈,並在承擔過濾職責時,將其派發到所有過濾器鏈的每一過過濾器上。

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);
				// 派發到過濾器鏈上
				doFilterInternal(request, response, chain);
			}
			finally {
				SecurityContextHolder.clearContext();
				request.removeAttribute(FILTER_APPLIED);
			}
		}
		else {
			doFilterInternal(request, response, chain);
		}
	}

FilterChainProxy#doFilterInternal 是真正執行虛擬過濾器鏈邏輯的方法

FilterChainProxy#doFilterInternal

	private void doFilterInternal(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		// 附上 Spring Security 提供的 Http 防火牆
		FirewalledRequest fwRequest = firewall
				.getFirewalledRequest((HttpServletRequest) request);
		HttpServletResponse fwResponse = firewall
				.getFirewalledResponse((HttpServletResponse) response);
		// 按照配置的 RequestMatcher ,決定每一個請求會經過哪些過濾器
		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();
            // 模擬過濾器的執行流程,執行整條過濾器鏈
			chain.doFilter(fwRequest, fwResponse);

			return;
		}

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


	private static class VirtualFilterChain implements FilterChain {
		private final FilterChain originalChain;
		private final List<Filter> additionalFilters;
		private final FirewalledRequest firewalledRequest;
		private final int size;
		private int currentPosition = 0;

		private VirtualFilterChain(FirewalledRequest firewalledRequest,
				FilterChain chain, List<Filter> additionalFilters) {
			this.originalChain = chain;
			this.additionalFilters = additionalFilters;
			this.size = additionalFilters.size();
			this.firewalledRequest = firewalledRequest;
		}

		@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();
				// 執行過濾器鏈後,調用真實的 FilterChain,完成原生過濾器的剩餘邏輯
				originalChain.doFilter(request, response);
			}
			else {
				currentPosition++;

				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() + "'");
				}
				// 通過改變下標回調的方式按照順序執行每一個過濾器
				nextFilter.doFilter(request, response, this);
			}
		}
	}

碼字不易,如果覺得這篇博客對你有用可以點贊鼓勵一下。

參考:

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