SpringSecurity 原始碼分析之SecurityFilterchain的構建

mingtiandexia發表於2019-03-28

基本原理

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
  1. security starter autoconfigure 自動注入的
    SpringBootWebSecurityConfiguration$IgnoredPathsWebSecurityConfigurerAdapter
    SpringBootWebSecurityConfiguration$ApplicationWebSecurityConfigurerAdapter
  2. 用於管理的,暫時沒看到在哪個地方注入的
    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()

應用的設計思想分析:

  1. 模板方法模式:
    公共的配置如init(),configure()抽象到父類,
    把需要具體物件實現的放到了子類實現.
    如beforeInit(),beforeConfigure();performBuild做為抽象方法,需要具體的物件去實現.
  2. 泛型與策略模式的靈活運用
    雖然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物件,然後進行生成
![(https://img-blog.csdnimg.cn/20190331102613187.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21pbmd0aWFuZGV4aWE=,size_16,color_FFFFFF,t_70)
可以看到這裡有三個securityFilterChainBuilder,每個securityFilterChainBuilder裡面包含configures,sharedObjects等物件
securityFilterChainBuilder.build就是根據這些配置檔案來生成filter的,再次呼叫AbstractConfiguredSecurityBuilder#doBuild如果

整理

  • WebSecurityConfiguration#springSecurityFilterChain
    • WebSecurity#build()
      • AbstractSecurityBuilder#build()->dobuild()
        • AbstractConfiguredSecurityBuilder#doBuild
            1. init()
              遍歷所有的configurer,呼叫init方法
            1. beforeConfigure()
              抽象方法,呼叫WebSecurity.beforeConfirue()
            1. configure()
              遍歷所有的configure,呼叫configure()方法
            1. performBuild
              抽象方法,呼叫WebSecurity.performBuild()

上面的流程,第2步與第4步呼叫WebSecurity這個類的方法,因此最大的變化在第1,3步,裡面

BrowserSecurityConfig
//security starter autoconfigure 自動注入的
SpringBootWebSecurityConfigurationIgnoredPathsWebSecurityConfigurerAdapterSpringBootWebSecurityConfigurationIgnoredPathsWebSecurityConfigurerAdapter SpringBootWebSecurityConfigurationApplicationWebSecurityConfigurerAdapter
//用於管理的,暫時沒看到在哪個地方注入的
ManagementWebSecurityAutoConfigurationManagementWebSecurityConfigurerAdapter//securitystarterautoconfigureSpringBootWebSecurityConfigurationManagementWebSecurityConfigurerAdapter //security starter autoconfigure 自動注入的 SpringBootWebSecurityConfigurationIgnoredPathsWebSecurityConfigurerAdapter
SpringBootWebSecurityConfigurationApplicationWebSecurityConfigurerAdapter//ManagementWebSecurityAutoConfigurationApplicationWebSecurityConfigurerAdapter //用於管理的,暫時沒看到在哪個地方注入的 ManagementWebSecurityAutoConfigurationManagementWebSecurityConfigurerAdapter

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);
			}
		});
	}

相關文章