Spring Security的核心實現是通過一條過濾器鏈來確定用戶的每一個請求應該得到什麼樣的反饋。
1. 使用@EnableWebSecurity註解開啓Spring Security
在使用Spring Security時首先要通過@EnableWebSecurity註解開啓Spring Security的默認行爲。
@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 {
boolean debug() default false;
}
該註解通過@Import註解將WebSecurityConfiguration類導入到Spring 的IoC容器,從而對Spring Security進行初始化。同時,@EnableWebSecurity可以通過配置debug = true開啓調試模式,能夠打印出Spring Security運行時的詳細信息,如下:
接下來,我們看看上圖中的過濾器是如何加載到過濾器鏈中的。
2. WebSecurityConfiguration
首先看WebSecurityConfiguration類中的setFilterChainProxySecurityConfigurer函數,該函數用來初始化SecurityConfigurer列表:
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
(1)@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
(2)webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
(3)webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
(4)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;
}
(5)for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
(1)在傳入的參數中,objectPostProcess用於初始化對象,暫時不用關注。webSecurityConfigurers是通過SpEL調用Bean的方法獲得的值,其獲得的是我們在配置Spring Security時繼承自WebSecurityConfigurerAdapter的配置類(具體代碼見:AutowiredWebSecurityConfigurersIgnoreParents類的getWebSecurityConfigurers方法)。
(2)初始化WebSecurity。
(3)對webSecurityConfigurers按升序進行排序(排序算法是穩定的),如果一個應用中有多個SecurityConfigurer,可通過@Order註解指定其順序,註解中的值越大,SecurityConfigurer排序後越靠後。繼承自WebSecurityConfigurerAdapter的配置類其@Order註解的默認值爲100。該步驟的作用是爲第(4)步檢查重複的@Order註解值做準備。
(4)檢查多個SecurityConfigurer配置的@Order註解值是否相同,如果相同則報錯。這就說明如果代碼中通過繼承自WebSecurityConfigurerAdapter配置了多個SecurityConfigurer,則必須爲每個SecurityConfigurer設置@Order註解,並且註解值不能相同。
(5)將配置的每一個SecurityConfigurer應用到WebSecurity。這裏我們順便看下WebSecurity類的apply函數(WebSecurity繼承自AbstractConfiguredSecurityBuilder,apply函數位於AbstractConfiguredSecurityBuilder中。),因爲後面的代碼分析會用到。
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized (configurers) {
if (buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer
\+ " to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<>(1);
}
configs.add(configurer);
(6)this.configurers.put(clazz, configs);
if (buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
可以看到apply函數主要調用add函數,並在add函數中將SecurityConfigurer添加到WebSecurity類的configures屬性,鍵值爲clazz(第(6)步)。注意此時WebSecurity中的buildState的狀態爲UNBUILT。
回到WebSecurityConfiguration中,我們再看其另一個函數:springSecurityFilterChain,該函數返回Filter,用於創建Spring Security過濾器鏈,代碼如下:
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
(7)if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
(8)return webSecurity.build();
}
(7) 如果沒有通過繼承WebSecurityConfigurerAdatper類配置過Spring Security,則會以WebSecurityConfigurerAdatper中的配置默認行爲。
(8) 調用WebSecurity類的build方法構建過濾器鏈。
接下來將講到最核心的過濾器鏈的構建,首先看下WebSecurity類中的build函數(WebSecurity繼承自AbstractConfiguredSecurityBuilder類,而後者又繼承自AbstractSecurityBuilder類,build函數位於AbstractSecurityBuilder類中),代碼爲:
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
(9)this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
可以看到最終調用的是doBuild函數(位於AbstractConfiguredSecurityBuilder類中),代碼如下:
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
(10)beforeInit();
(11)init();
buildState = BuildState.CONFIGURING;
(12)beforeConfigure();
(13)configure();
buildState = BuildState.BUILDING;
(14)O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
(10) 默認該函數爲空。
(11) 初始化SecurityConfigurer,初始化的SecurityConfigurer爲前面講的setFilterChainProxySecurityConfigurer函數調用apply函數時設置的,最終調用的是WebSecurityConfigurerAdapter的init函數,將所有的HttpSecurity添加到WebSecurity中。
(12) 默認該函數爲空。
(13) 調用WebSecurityConfigurerAdapter中的configure(WebSecurity web)函數,默認爲空。
(14) 完成過濾器鏈的構建。
從上面的步驟可以看出,只有第(11)和第(14)步才真正做了操作,而第(14)步纔是真正構建過濾器鏈的操作。因此,接下來將看performBuild函數(位於WebSecurity類中),代碼如下:
@Override
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));
}
(15)for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
(16)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();
(17)return result;
}
(15) 爲每一個securityFilterChainBuilder生成過濾器鏈,securityFilterChainBuilders集合中的內容是我們在第(11)步中設置的HttpSecurity。這裏請注意一點,即WebSecurity和HttpSecurity有相同的繼承結構,如下:
因此,參考步驟(10)~(14),可以確定最終調用的是HttpSecurity下的performBuild函數,如下:
@Override
protected DefaultSecurityFilterChain performBuild() {
filters.sort(comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
可以看到最終返回的是DefaultSecurityFilterChain類,該類中包含了過濾器鏈中的所有過濾器。
(16) 將生成的過濾器鏈securityFilterChains由FilterChainProxy來代理,FilterChainProxy間接實現了Filter接口。
(17) 將FilterChainProxy對象返回
以上就是是過濾器鏈的生成過成,目前遺留了兩個問題在接下來的文章中分析:
-
在步驟(15)中,我們說HttpSecurity類中的performBuild函數返回了DefaultSecurityFilterChain類,而該類中封裝了該鏈的所有過濾器,那麼這些過濾器是如何添加進來的呢?
-
在步驟(17)中生成的FilterChainProxy對象也是Filter實例,那麼在收到客戶端請求後是如何完成過濾器鏈式調用的呢?