SpringSecurity 原始碼分析之SecurityFilterchain的構建
基本原理
spring security通過一系列filter來對請求進行攔截
網上一個比較好的圖
由於是一個filterChain,因此如果我在FilterSecrutiyInterceptor,即最後一個過濾器上打斷點,一定能夠通過debug的方式來得到整個過濾器鏈條
在最後一個filter.doFilter方法上打一個斷點,
得到整個filterChain,進一步驗證了上面圖的正確性。
但是這些filter是什麼時候新增進去的呢?從security-start的自動配置類開始分析
security-start 自動配置原理
1. SecurityAutoConfiguration類中匯入了SpringBootWebSecurityConfiguration
@Import({ SpringBootWebSecurityConfiguration.class,
AuthenticationManagerConfiguration.class,
BootGlobalAuthenticationConfiguration.class, SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
2.SpringBootWebSecurityConfiguration中加了@EnableWebSecurity
@EnableWebSecurity
public class SpringBootWebSecurityConfiguration {
3.EnableWebSecurity註解中匯入了WebSecurityConfiguration類
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
FilterChain的構造過程
1. WebSecurityConfiguration
這是配置類的入口類
1.1 setFilterChainProxySecurityConfigurer
@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));
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;
}
這個類的主要作用
1.1.1 構建WebSecurity物件
1.1.2 獲得security相關的配置
- 通過這個呼叫BeanFactory中Bean的名字為autowiredWebSecurityConfigurersIgnoreParents的getWebSecurityConfigurers方法得到webSecurityConfigurers,並把這些配置加入到webSecurity物件中
通過debug可以看到有4個,
1. 自己定義的安全相關配置類
BrowserSecurityConfig
- security starter autoconfigure 自動注入的
SpringBootWebSecurityConfiguration$IgnoredPathsWebSecurityConfigurerAdapter
SpringBootWebSecurityConfiguration$ApplicationWebSecurityConfigurerAdapter- 用於管理的,暫時沒看到在哪個地方注入的
ManagementWebSecurityAutoConfiguration$ManagementWebSecurityConfigurerAdapter
1.2 呼叫springSecurityFilterChain方法來生成FiterChain
這個方法是個模板方法,定義了整個生成的骨架
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
//1.init,呼叫上面每個webSecurityConfigurers的init
init();
buildState = BuildState.CONFIGURING;
//2. 設定AuthenticationManager
beforeConfigure();
//3. 呼叫上面每個webSecurityConfigurers的configure方法
configure();
buildState = BuildState.BUILDING;
//4.生成filterChain
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
- WebSecurity UML類圖
webSecurity.build()
應用的設計思想分析:
- 模板方法模式:
公共的配置如init(),configure()抽象到父類,
把需要具體物件實現的放到了子類實現.
如beforeInit(),beforeConfigure();performBuild做為抽象方法,需要具體的物件去實現. - 泛型與策略模式的靈活運用
雖然init(),confiure()是公共的方法,但是針對不同型別的操作是不一樣的,
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
這樣定義傳入一個this,如果是http,就會傳入httpsecurity的引數,如果是web就傳入websecurity型別的引數,有點類似於策略模式
如何生成具體的filterChain,當然這是呼叫的是WebSecurity的方法,
protected Filter performBuild() throws Exception {
...
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(
chainSize);
//1.首先新增不需要認證的url過濾器,這裡面的url是靜態資原始檔目錄
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
//2.新增需要經過安全認證的過濾器
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
//3.生成過濾器的代理
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
//4.真正的build方法執行
postBuildAction.run();
return result;
}
上面的關鍵點是securityFilterChainBuilder.build()這個方法,securityFilterChainBuilder是SecurityBuilder型別的例項,從上面的類圖來看,httpsecurity也是這個介面的例項,實際上這個就是init()方法中建立的httpsecurity物件
上面的分析是大概的整體flow,現拿一個做為例子:
BrowserSecurityConfig如何生成過濾器鏈
- WebSecurity.build()->AbstractConfiguredSecurityBuilder#doBuild.build
BrowserSecurityConfig.init->beforeConfigure->configure->performBuild->postBuildAction.run();
1.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);
}
});
}
gethttp()->to create the http object and set the default param
@SuppressWarnings({ "rawtypes", "unchecked" })
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
//設定後置處理器?
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
//獲得AuthenticationManager
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
//新增一系列filter的預設配置
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
//增加使用者自定義的配置,重寫這個方法,就會對上面的security chain進行新增或修改
configure(http);
return http;
}
關於泛型的宣告
//宣告
public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
//例項
public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractHttpConfigurer<AnonymousConfigurer<H>, H> {
public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter>
extends AbstractHttpConfigurer<T, B> {
performBuild
protected Filter performBuild() throws Exception {
...
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(
chainSize);
//1.首先新增不需要認證的url過濾器,這裡面的url是靜態資原始檔目錄
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
//2.新增需要經過安全認證的過濾器
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
//3.生成過濾器的代理
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
//4.真正的build方法執行
postBuildAction.run();
return result;
}
需要拿 到所有的securityFilterChainBuilder,即init中建立的httpSecurity物件,然後進行生成
可以看到這裡有三個securityFilterChainBuilder,每個securityFilterChainBuilder裡面包含configures,sharedObjects等物件
securityFilterChainBuilder.build就是根據這些配置檔案來生成filter的,再次呼叫AbstractConfiguredSecurityBuilder#doBuild如果
整理
- WebSecurityConfiguration#springSecurityFilterChain
- WebSecurity#build()
- AbstractSecurityBuilder#build()->dobuild()
- AbstractConfiguredSecurityBuilder#doBuild
-
- init()
遍歷所有的configurer,呼叫init方法
- init()
-
- beforeConfigure()
抽象方法,呼叫WebSecurity.beforeConfirue()
- beforeConfigure()
-
- configure()
遍歷所有的configure,呼叫configure()方法
- configure()
-
- performBuild
抽象方法,呼叫WebSecurity.performBuild()
- performBuild
-
- AbstractConfiguredSecurityBuilder#doBuild
- AbstractSecurityBuilder#build()->dobuild()
- WebSecurity#build()
上面的流程,第2步與第4步呼叫WebSecurity這個類的方法,因此最大的變化在第1,3步,裡面
BrowserSecurityConfig
//security starter autoconfigure 自動注入的
SpringBootWebSecurityConfigurationApplicationWebSecurityConfigurerAdapter
//用於管理的,暫時沒看到在哪個地方注入的
ManagementWebSecurityAutoConfigurationIgnoredPathsWebSecurityConfigurerAdapter
SpringBootWebSecurityConfigurationManagementWebSecurityConfigurerAdapter
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);
}
});
}
這裡最主要的是getHttp這個方法,檢視這個方法,發現構建了http請求,並且最後也呼叫了configure(http)來配置http
也就是說如果我們自定義的類重寫了這個方法,那麼就用的是我們自定義的。
Q:如果這裡也呼叫了configure方法,那麼上面那第三步的config方法是不是就沒有必要呼叫了?是我自己理解有誤嗎?
A:的確是理解有誤,所有的構建在init中即第1步已經完成了,第3步中的configure,
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
可以看到這裡引數是泛型B,傳遞的是this物件,由開始知道,我們呼叫的是WebSecurity,而不是HttpSecurity物件,
因此這裡呼叫 的下面這個方法,其實是個空方法(以前以為configure(HttpSecurity http)),
public void configure(WebSecurity web) throws Exception {
}
Q:按這個邏輯,最後執行performBuild,來構建整個filter鏈,沒啥問題,
問題是這個方法中會呼叫
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
再次掉入了這個建立的迴圈中
A: AbstractConfiguredSecurityBuilder#configure
並未進入迴圈,這裡的getConfigurres為null
最後一步的performBuild,呼叫的也是authenticationManagerBuilder#performBuild,並不是前面的performBuild
如果當前物件是httpSecurity,則呼叫所有configure方法,生成對應的filterchain
Q:哪裡生成SecurityBuilder?
A:WebSecurityConfigurerAdapter#init方法中新增了,這裡即HttpSecurity這個物件
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);
}
});
}
相關文章
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- Java 容器系列(七):HashMap 原始碼分析01之建構函式、內部類JavaHashMap原始碼函式
- Android 原始碼分析(一)專案構建過程Android原始碼
- 深入Vue - 原始碼目錄及構建過程分析Vue原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- Android 原始碼分析之 EventBus 的原始碼解析Android原始碼
- Java原始碼分析:Guava之不可變集合ImmutableMap的原始碼分析Java原始碼Guava
- 原始碼分析之 HashMap原始碼HashMap
- 原始碼分析之AbstractQueuedSynchronizer原始碼
- 原始碼分析之ArrayList原始碼
- 【Tomcat 原始碼系列】原始碼構建 TomcatTomcat原始碼
- Vue.js 原始碼構建Vue.js原始碼
- AvaloniaChat—從原始碼構建指南原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- redis原始碼分析(二)、redis原始碼分析之sds字串Redis原始碼字串
- MongoDB原始碼分析之Command體系架構CJMongoDB原始碼架構
- SpringSecurity認證流程原始碼詳解SpringGse原始碼
- JDK1.8原始碼分析01之學習建議(可以延伸其他原始碼學習)JDK原始碼
- Flutter原始碼剖析(一):原始碼獲取與構建Flutter原始碼
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- Vue原始碼探究-構建版本的區別Vue原始碼
- 【SpringSecurity系列02】SpringSecurity 表單認證邏輯原始碼解讀SpringGse原始碼
- JUC之CountDownLatch原始碼分析CountDownLatch原始碼
- Dubbo之SPI原始碼分析原始碼
- Fresco原始碼分析之DraweeView原始碼View
- lodash原始碼分析之isArguments原始碼
- Fresco原始碼分析之Hierarchy原始碼
- 原始碼分析Kafka之Producer原始碼Kafka
- RecyclerView之SnapHelper原始碼分析View原始碼
- OpenGL 之 GPUImage 原始碼分析GPUUI原始碼
- lodash原始碼分析之isObjectLike原始碼Object
- 原始碼分析之 LinkedList原始碼
- Envoy原始碼分析之Dispatcher原始碼
- Redux原始碼分析之createStoreRedux原始碼
- MongoDB原始碼分析之MongosXFMongoDB原始碼
- jdk原始碼分析之TreeMapJDK原始碼
- 原始碼分析之ThreadPoolExecutor原始碼thread