最近在整合微服務OAuth 2認證過程當中,它是基於Spring Security之上,而本人對Spring Security架構原理並不太熟悉,致使不少配置搞不太清楚,遂咬牙啃完了Spring Security核心源碼,花了差很少一星期,整體上來講,其代碼確實比較晦澀,以前在學習Apache Shiro框架以前也曾經在相關論壇裏瞭解過,相比Spring Security,Apache Shiro真的是至關輕量,代碼研讀起來容易不少,而Spring Security類繼承結構複雜,大量使用了其所謂Builder和Configuer模式,其代碼跟蹤過程很痛苦,遂記錄下,分享給有須要的人,因爲本人能力有限,在文章中有不對之處,還請各位執教,在此謝謝各位了。css
本人研讀的Spring Security版本爲:5.1.4.RELEASE
java
Spring Security在3.2版本以後支持Java Configuration,即:經過Java編碼形式配置Spring Security,可再也不依賴XML文件配置,本文采用Java Configuration方式。web
在Spring Security官方文檔中有一個最簡配置例子:spring
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}複製代碼
咱們先不要看其它內容,先關注註解@EnableWebSecurity
,它是初始化Spring Security的入口,打開其源碼以下:bash
@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;
}複製代碼
該註解類經過@Configuration
和@Import
配合使用引入了一個配置類(WebSecurityConfiguration
)和兩個ImportSelector(SpringWebMvcImportSelector
,OAuth2ImportSelector
),咱們重點關注下WebSecurityConfiguration
,它是Spring Security的核心,正是它構建初始化了全部的Bean實例和相關配置,下面咱們詳細分析下。session
打開WebSecurityConfiguration
源碼,發現它被@Configuration
標記,說明它是配置類,架構
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware 複製代碼
該類中最重要的工做就是實例並註冊FilterChainProxy
,也就是咱們在之前XML文件中配置的過濾器:app
<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>複製代碼
該過濾器負責攔截請求,並把請求經過必定的匹配規則(經過RequestMatcher匹配實現)路由(或者Delegate)到具體的SecurityFilterChain
,源碼以下:框架
/** * Creates the Spring Security Filter Chain * @return the {@link Filter} that represents the security filter chain * @throws Exception */
@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();
}複製代碼
@Bean
註解name
屬性值AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME
就是XML中定義的springSecurityFilterChain
。ide
從源碼中知道過濾器經過最後的webSecurity.build()
建立,webSecurity
的類型爲:WebSecurity
,它在setFilterChainProxySecurityConfigurer
方法中優先被建立了:
/** * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} * instances used to create the web configuration. * * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a * {@link WebSecurity} instance * @param webSecurityConfigurers the * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to * create the web configuration * @throws Exception */
@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);
}
Collections.sort(webSecurityConfigurers, 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;
}複製代碼
從代碼中能夠看到,它是直接被new出來的:
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));複製代碼
setFilterChainProxySecurityConfigurer方法參數中須要被注入兩個對象:objectPostProcessor
和webSecurityConfigurers
,objectPostProcessor
是在ObjectPostProcessorConfiguration
配置類中註冊的,而webSecurityConfigurers
則是使用了@Value
註解方式,註解內容爲:#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}
,經過源碼瞭解,autowiredWebSecurityConfigurersIgnoreParents
是在本類中被註冊:
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents( ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}複製代碼
在AutowiredWebSecurityConfigurersIgnoreParents中定義了方法:getWebSecurityConfigurers
:
@SuppressWarnings({ "rawtypes", "unchecked" })
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}複製代碼
它經過BeanFactory獲取了類型爲WebSecurityConfigurer
的Bean實例列表。回到WebSecurityConfiguration
類中的setFilterChainProxySecurityConfigurer
方法,它把WebSecurityConfigurer
列表設置到了WebSecurity
中,源碼以下:
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}複製代碼
經過apply
方法,apply方法其實就是webSecurityConfigurer
放入webSecurity
維護的configurers
屬性中,configurers
是個LinkedHashMap
,源碼以下:
/** * Applies a {@link SecurityConfigurer} to this {@link SecurityBuilder} overriding any * {@link SecurityConfigurer} of the exact same class. Note that object hierarchies * are not considered. * * @param configurer * @return the {@link SecurityConfigurerAdapter} for further customizations * @throws Exception */
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}複製代碼
其中代碼add(configurer)
就是將這些webSecurityConfigurer
添加到webSecurity
的configurers
屬性中。
如今webSecurity
的初始化工做已經完成,如今回到springSecurityFilterChain
方法中,它首先檢查當前是否配置了webSecurityConfigurer
,若是沒有的會默認設置一個,而且調用上面提到的apply
方法,源碼以下:
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}複製代碼
若是已經存在配置了webSecurityConfigurer
,則調用webSecurity.build()
進行構建。
在進入build
方法以前,首先簡單介紹下WebSecurity的繼承結構,
它實現了SecurityBuilder
接口,繼承自AbstractConfiguredSecurityBuilder
,AbstractConfiguredSecurityBuilder
繼承自AbstractSecurityBuilder
,AbstractSecurityBuilder
實現了SecurityBuilder
,其中AbstractConfiguredSecurityBuilder
實現了經過自定義SecurityConfigurer
類來配置SecurityBuilder
,上面提到的apply(SecurityConfigurer configurer)
就是在該類中實現的,它把configurer保存在它維護的LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>()
中。
調用webSecurity.build()
後,首先調用的父類AbstractSecurityBuilder
中的build
方法:
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}複製代碼
而後調用doBuild()
,doBuild()
在子類中實現,AbstractConfiguredSecurityBuilder
實現了該方法:
@Override
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;
}
}複製代碼
在這裏重點關注init()
、configure()
和performBuild()
,下面逐個分析它們的做用。
init()
方法在AbstractConfiguredSecurityBuilder
實現:
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}複製代碼
它的工做是迭代調用全部配置的SecurityConfigrer
的init
方法,在這裏實際上是它的子類WebSecurityConfigurer
,由於以前獲取時指定的類型就是WebSecurityConfigurer
,在上文中提到AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()
中:
Map<String, WebSecurityConfigurer> beansOfType = beanFactory.getBeansOfType(WebSecurityConfigurer.class);複製代碼
而實現了WebSecurityConfigurer
接口的就是WebSecurityConfigurerAdapter
,WebSecurityConfigurerAdapter.init()
源碼以下:
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}複製代碼
它只要完成兩件重要的事情:
HttpSecurity
對象;HttpSecurity
對象添加至WebSecurity
的securityFilterChainBuilders
列表中;初始化HttpSecurity
對象在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<? extends Object>, 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;
}複製代碼
從代碼中能夠了解,HttpSecurity
是直接被new出來的,在建立HttpSecurity
以前,首先初始化了AuthenticationManagerBuilder
對象,這裏有段代碼很重要就是: AuthenticationManager authenticationManager = authenticationManager();
,它建立AuthenticationManager
實例,打開authenticationManager()
方法:
protected AuthenticationManager authenticationManager() throws Exception {
if (!authenticationManagerInitialized) {
configure(localConfigureAuthenticationBldr);
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
authenticationManager = localConfigureAuthenticationBldr.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}複製代碼
在初始化時,它會調用configure(localConfigureAuthenticationBldr);
,默認的實現是:
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}複製代碼
【一、個性化配置入口之configure(AuthenticationManagerBuilder auth)
】
咱們能夠經過繼承WebSecurityConfigurerAdapter
並重寫該方法來個性化配置AuthenticationManager
。
構建完authenticationManager
實例後,將它設置爲authenticationBuilder
的父認證管理器:
authenticationBuilder.parentAuthenticationManager(authenticationManager);複製代碼
並將該authenticationBuilder
傳入HttpSecurity
構造器構建HttpSecurity
實例。
構建完HttpSecurity
實例後,默認狀況下會添加默認的攔截其配置:
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();複製代碼
最後調用configure(http);
,這又是一個可個性化的配置入口,它的默認實現是:
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}複製代碼
默認的配置是攔截全部的請求須要認證以後才能訪問,若是沒有認證,會自動生成一個認證表單要求輸入用戶名和密碼。
【二、個性化配置入口之configure(HttpSecurity http)
】
咱們能夠經過繼承WebSecurityConfigurerAdapter
並重寫該方法來個性化配置HttpSecurity
。
OK,目前爲止HttpSecurity
已經被初始化,接下去須要設置HttpSecurity
對象添加至WebSecurity
的securityFilterChainBuilders
列表中:
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});複製代碼
打開HttpSecurity
類結構,和WebSecurity同樣,它也實現了SecurityBuilder
接口,一樣繼承自AbstractConfiguredSecurityBuilder
。
當全部的WebSecurityConfigurer
的init
方法被調用以後,webSecurity.init()
工做就結束了。
接下去調用了webSecurity.configure()
,該方法一樣是在AbstractConfiguredSecurityBuilder
中實現的:
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}複製代碼
它的主要工做是迭代調用全部WebSecurityConfigurer
的configurer
方法,參數是WebSeucrity
自己,這又是另一個重要的個性化入口:
【三、個性化配置入口之configure(WebSecurity web)
】
咱們能夠經過繼承WebSecurityConfigurerAdapter
並重寫該方法來個性化配置WebSecurity
。
自此,三個重要的個性化入口都已經被調用,即在實現WebSecurityConfigurerAdapter
常常須要重寫的:
一、configure(AuthenticationManagerBuilder auth);
二、configure(WebSecurity web);
三、configure(HttpSecurity http);複製代碼
回到webSecurity構建過程,接下去重要的的調用:
O result = performBuild();複製代碼
該方法在WebSecurityConfigurerAdapter
中實現,返回的就是過濾器FilterChainProxy,源碼以下:
@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));
}
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;
}複製代碼
首先計算出chainSize
,也就是ignoredRequests.size() + securityFilterChainBuilders.size();
,若是你不配置ignoredRequests
,那就是securityFilterChainBuilders.size()
,也就是HttpSecurity
的個數,其本質上就是你一共配置幾個WebSecurityConfigurerAdapter
,由於每一個WebSecurityConfigurerAdapter
對應一個HttpSecurity
,而所謂的ignoredRequests
就是FilterChainProxy
的請求,默認是沒有的,若是你須要條跳過某些請求不須要認證或受權,能夠以下配置:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/statics/**");
}複製代碼
在上面配置中,全部以/statics
開頭請求都將被FilterChainProxy
忽略。
計算完chainSize
後,就會建立List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
,遍歷全部的HttpSecurity
,調用HtppSecurity
的build()
構建其對應的過濾器鏈SecurityFilterChain
實例,並將SecurityFilterChain
添加到securityFilterChains
列表中:
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}複製代碼
調用HtppSecurity
的build()
構建其實和調用WebSecurity
的build()
構建類相似,父類中方法一次被執行,最後執行自己的performBuild()
方法,其源碼以下:
@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}複製代碼
構建SecurityFilterChain
主要是完成RequestMatcher
和對應的過濾器列表,咱們都知道在Spring Security中,過濾器執行按順序順序的,這個排序就是在performBuild()
中完成的,也就是:
Collections.sort(filters, comparator);複製代碼
它經過一個比較器實現了過濾器的排序,這個比較器就是FilterComparator
,有興趣的朋友能夠本身去了解詳情。
最後返回的是SecurityFilterChain
的默認實現DefaultSecurityFilterChain
。
構建完全部SecurityFilterChain
後,建立最爲重要的FilterChainProxy
實例,
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);複製代碼
構造器中傳入SecurityFilterChain
列表,若是開啓了Debug模式,還會被包裝成DebugFilter
類型,共開發調試使用,默認是關閉的,能夠經過過下面方式開啓Debug模式:
@Override
public void configure(WebSecurity web) throws Exception {
web.debug(true);
}複製代碼
至此Spring Security 初始化完成,咱們經過繼承WebSecurityConfigurerAdapter
來代達到個性化配置目的,文中提到了三個重要的個性化入口,而且WebSecurityConfigurerAdapter
是能夠配置多個的,其對應的接口就是會存在多個SecurityFilterChain
實例,可是它們人仍然在同一個FilterChainProxy中,經過RequestMatcher
來匹配並傳入到對應的SecurityFilterChain
中執行請求。