springboot情操陶冶-web配置(九)

承接前文springboot情操陶冶-web配置(八),本文在前文的基礎上深刻了解下WebSecurity類的運做邏輯html

WebSecurityConfigurerAdapter

在剖析WebSecurity的工做邏輯以前,先預熱下springboot security推薦複寫的抽象類WebSecurityConfigurerAdapter,觀察下其是如何被實例化的,方便後續的深刻理解java


1.ApplicationContext環境設置web

@Autowired
	public void setApplicationContext(ApplicationContext context) {
		this.context = context;

		ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
		LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);
		// 
		authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);
		localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
			@Override
			public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
				authenticationBuilder.eraseCredentials(eraseCredentials);
				return super.eraseCredentials(eraseCredentials);
			}

		};
	}

同前文的認證管理器建立差很少,但這裏細心的能夠看到其內部建立了兩個如出一轍的認證管理器對象,目的應該是爲了引用AuthenticationConfiguration對象中的認證管理器做準備,下文會說起spring


2.引入前文所提的AuthenticationConfiguration對象緩存

@Autowired
	public void setAuthenticationConfiguration(
			AuthenticationConfiguration authenticationConfiguration) {
		this.authenticationConfiguration = authenticationConfiguration;
	}

目的是間接調用此類獲取對應的認證管理器AuthenticationManager對象,具體的讀者可自行閱讀安全


3.共享變量集合,security板塊引入了共享變量,目的就跟緩存同樣springboot

private Map<Class<? extends Object>, Object> createSharedObjects() {
		Map<Class<? extends Object>, Object> sharedObjects = new HashMap<Class<? extends Object>, Object>();
		sharedObjects.putAll(localConfigureAuthenticationBldr.getSharedObjects());
		sharedObjects.put(UserDetailsService.class, userDetailsService());
		sharedObjects.put(ApplicationContext.class, context);
		sharedObjects.put(ContentNegotiationStrategy.class, contentNegotiationStrategy);
		sharedObjects.put(AuthenticationTrustResolver.class, trustResolver);
		return sharedObjects;
	}

OK,差很少就這樣,開始咱們的主角之旅把session

WebSecurityConfiguration

根據前文可知,WebSecurity對象的建立與運做都是經過WebSecurityConfiguration來的,筆者在此處再做下簡單的概括 1.實例化WebSecurity對象並註冊至ApplicationContext上下文對象中 2.獲取ApplicationContext對象上註冊的類型爲WebSecurityConfigurer的接口集合,存放至WebSecurity的父類AbstractConfiguredSecurityBuilder的內部屬性*configurers(hashmap)中 3.運行WebSecurity的build()*方法建立Filter過濾鏈app

基於上述的結論咱們再着重看下第三點的建立過濾鏈是如何完成的,本文側重點也在於此ide

WebSecurity#build()

*build()*方法是由其父類AbstractSecurityBuilder來完成的,代碼很簡單

public final O build() throws Exception {
		// 確保只會被build一次
		if (this.building.compareAndSet(false, true)) {
			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;

			// init
			beforeInit();
			init();

			buildState = BuildState.CONFIGURING;

			// configure
			beforeConfigure();
			configure();

			buildState = BuildState.BUILDING;

			// build
			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
	}

大體能夠分爲三步走,下面筆者就按照上述的三步一一分開剖析

初始化階段

分爲*beforeInit()init()*兩個操做

beforeInit()

beforeInit()初始化前的操做,默認是空的,可供用戶去複寫

init()

init()初始化方法比較有意思了,看下其源碼

private void init() throws Exception {
		// WebSecurity內的configurer是WebSecurityConfigurer類型的
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

		// 遍歷調用公用的init()方法,關注此處的this指代WebSecurity對象
		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.init((B) this);
		}

		// 候補
		for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
			configurer.init((B) this);
		}
	}

好,筆者來看下*configurer#init()*方法,此處只針對WebSecurityConfigurer接口的初始化。即便用戶沒有去實現該接口,springboot也會默認有個類去實現

@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {

	@Configuration
	@Order(SecurityProperties.BASIC_AUTH_ORDER)
	static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

	}

}

很明顯,就是繼承WebSecurityConfigurerAdapter類來實現的,那咱們看下其*init()*方法的操做

public void init(final WebSecurity web) throws Exception {
		// important
		final HttpSecurity http = getHttp();
		// configure interceptor?
		web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
			public void run() {
				FilterSecurityInterceptor securityInterceptor = http
						.getSharedObject(FilterSecurityInterceptor.class);
				web.securityInterceptor(securityInterceptor);
			}
		});
	}

上述的代碼比較關鍵,筆者按兩個小步驟講述一下 1.建立HttpSecurity對象,貼出源碼仔細分析下

protected final HttpSecurity getHttp() throws Exception {
		if (http != null) {
			return http;
		}

		// 配置事件發行器
		DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
				.postProcess(new DefaultAuthenticationEventPublisher());
		localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

		// 獲取父級認證管理器,涉及configure(AuthenticationManagerBuilder auth)方法複寫
		AuthenticationManager authenticationManager = authenticationManager();
		authenticationBuilder.parentAuthenticationManager(authenticationManager);
		Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();

		// 建立HttpSecurity對象
		http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
				sharedObjects);
		// 是否屏蔽默認配置,默認爲false
		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();
			// 加載spring.factories中以AbstractHttpConfigurer做爲Key的類型集合
			List<AbstractHttpConfigurer> defaultHttpConfigurers =
					SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

			for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
				http.apply(configurer);
			}
		}
		// 供用戶自定義配置HTTP安全配置,默認支持表單和Basic方式提交認證信息
		configure(http);
		return http;
	}

代碼信息過多,不一一分析了,springboot推薦用戶複寫如下兩個方法

  • configure(AuthenticationManagerBuilder auth) 配置認證管理器,用戶信息讀取方式、加密方式都可經過此方法配置
  • configure(HttpSecurity http) 配置http服務,路徑攔截、csrf保護等等都可經過此方法配置

2.將HttpSecurity放入WebSecurity中,細看發現HttpSecurity是用來建立FilterChain過濾鏈的。並嘗試塞入一個FilterSecurityInterceptor攔截器,默認通常都是會配置的。

配置階段

分爲*beforeConfigure()configure()*階段

beforeConfigure()

配置前的操做,供用戶去複寫

configure()

遍歷其下的全部的WebSecurityConfigurerAdapter的實現類,統一調用*configure(WebSecurity web)*配置。很明顯,用戶也能夠複寫方法來配置,好比對HttpSecurity默認的配置不滿意,也能夠經過此類來複寫之;屏蔽一些URL的訪問等等。

建立階段

真正的建立Filter對象是由*performBuild()*方法執行的,別看源碼很長,其實就一個意思,經過HttpSecurity對象來建立Filter過濾鏈

@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);
		// 用戶可經過調用websecurity.ignoring()方法來屏蔽一些訪問路徑
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			// HttpSecurity#build()會被執行,執行邏輯就跟本文的WebSecurity如出一轍
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		// 防火牆配置
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		
		postBuildAction.run();
		return result;
	}

具體的用戶可自行去閱讀

小結

本文主要講述了WebSecurity與HttpSecurity之間的關係以及如何被建立,其實都是同樣的,它們都是AbstractConfiguredSecurityBuilder的複寫類,核心都是會執行其中的*build()*模板方法來建立過濾鏈。筆者或者讀者只須要複寫其中的幾個重要方法即可實現簡單的安全配置。但願本文對你們有用

相關文章
相關標籤/搜索