在之前的項目中使用接觸過通過 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
WebSecurity 在WebSecurityConfiguration#springSecurityFilterChain
中通過 WebSecurity#build
來創建 FilterChainProxy。也就是 Spring Security 的攔截器 Filter 鏈。
WebSecurity
實現了 SecurityBuilder
並通過 WebSecurity#performBuild
方法創建 FilterChainProxy
。
在 WebSecurity
的父類 AbstractConfiguredSecurityBuilder#doBuild
中,它的AbstractConfiguredSecurityBuilder#init
以及 AbstractConfiguredSecurityBuilder#configure
會獲取 SecurityConfigurer
來通過調用 SecurityConfigurer#init
和SecurityConfigurer#configure
對 SecurityBuilder
對象進行處理。
其實WebSecurity#performBuild
是通過SecurityBuilder#build
這個方法觸發的。Spring Security 通過 Java 配置來配置 SecurityBuilder
與 SecurityConfigurer
是非常重要的兩個接口。
1.5 SecurityBuilder
SecurityBuilder
是 Spring Security 定義用來 Spring Security 的對象創建接口。它只有一個接口 build
,它的核心實現類就是我們上面提到的 HttpSecurity
與 WebSecurity
。
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#init
與 SecurityConfigurer#configure
。SecurityConfigurer
接口的主要作用是用來配置 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
父類AbstractConfiguredSecurityBuilder
的AbstractConfiguredSecurityBuilder#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 |
DefaultLoginPageGeneratingFilter 、DefaultLogoutPageGeneratingFilter |
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 的核心實現是通過一條過濾器鏈來確定用戶的每一個請求應該得到什麼樣的反饋,如下圖所示:
通過 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 實戰》