SpringSecurity分析-1-啓動加載

網上已有一些對於SpringSecurity的分析解讀,寫的很好,看過以後受益良多!java

好比:web

https://www.cnkirito.moe/categories/Spring-Security/spring

http://www.spring4all.com/article/428json

有興趣能夠先研讀下數組

這裏的博客主要是參考他人文章,順便記錄下本身的理解,僅此安全

之前學習過Spring Security的XML配置形式,如今基本都使用Java Config形式了,這點的變化仍是蠻大的session

XML形式:mvc

<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>

這裏主要是註冊一個過濾器DelegatingFilterProxy類,映射的name爲springSecurityFilterChain,直觀。app

暫不分析async

Java Config形式:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
    //...
}

能夠看到,聲明一個配置類,主要特色有:

1:@EnableWebSecurity註解

2:繼承WebSecurityConfigurerAdapter類

3:重寫config方法

4:HttpSecurity類的使用

因爲被@Configuration修飾,不出意外,應該會在Spring啓動的時候加載。看下@EnableWebSecurity註解的定義:

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
}

是一個多重註解,加載了WebSecurityConfiguration.class,SpringWebMvcImportSelector.class和@EnableGlobalAuthentication註解,繼續跟進後者:

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}

發現其又加載AuthenticationConfiguration.class

綜上可知,主要是加載了三個類:

<1>SpringWebMvcImportSelector的做用是判斷當前的環境是否包含springmvc,由於spring security能夠在非spring環境下使用,爲了不DispatcherServlet的重複配置,因此使用了這個註解來區分。
<2> WebSecurityConfiguration顧名思義,是用來配置web安全的,下面的小節會詳細介紹。

<3>AuthenticationConfiguration權限配置相關類

而重點就是後二者!

下面啓動Spring,跟蹤下源碼,查看它如何加載的:

上述是啓動對應的時序圖。

由上所述,咱們主要關注三個起點類:

註解引入:WebSecurityConfiguration,AuthenticationConfiguration

自定義類的父類:WebSecurityConfigurerAdapter

自定義啓動類以下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@Configuration
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
	MyAuthenticationFailureHandler failHander = new MyAuthenticationFailureHandler();
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
//				.antMatchers("/")
//				.permitAll() // 請求路徑"/"容許訪問
//				.anyRequest()
//				.authenticated() // 其它請求都不須要校驗才能訪問
				.antMatchers("/home")
				.hasRole("LOGOPR")
			.and()
				.formLogin()
				.loginPage("/login") // 定義登陸的頁面"/login",容許訪問
				.permitAll()
				.failureUrl("/login?#error=1111")
//				.failureHandler(failHander)
			.and()
				.logout() // 默認的"/logout", 容許訪問
				.permitAll();
	}

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		System.out.println("--------------AuthenticationManagerBuilder-----------");
		// 在內存中注入一個用戶名爲anyCode密碼爲password而且身份爲USER的對象
		auth.inMemoryAuthentication().withUser("username").password("password").roles("USER");
	}

	/*
	 * 問題描述:在寫基於Spring cloud微服務的OAuth2認證服務時,由於Spring-Security從4+升級到5+,
	 * 致使There is no PasswordEncoder mapped for the id 「null」錯誤。
	 * */
	@Bean
	public static NoOpPasswordEncoder passwordEncoder() {
		return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
	}
}

debug啓動應用,進入AuthenticationConfiguration,生成了一個bean:AuthenticationManagerBuilder,以下:

是一個初始化的狀態,裏面的Providers爲空,parentAuthenticationManager爲空,狀態爲UNBUILT

接着經過setFilterChainProxySecurityConfigurer注入bean

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

其中包括咱們自定義的MyWebSecurityConfig代理類:

能夠看到是一個List類型,也說明能夠同時自定義多個Config類

 接着就new出一個WebSecurity對象,遍歷上述List,將自定義MyWebSecurityConfig應用於webSecurity:webSecurity.apply(webSecurityConfigurer)

這裏調用的是AbstractConfiguredSecurityBuilder

List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
					.get(clazz) : null;
if (configs == null) {
	configs = new ArrayList<SecurityConfigurer<O, B>>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);

主要是爲了初始化:

LinkedHashMap<Class,List<SecurityConfigurer>> configurers,

它存儲的是自定義的MyWebSecurityConfig,key是類對應的Class,value是此List<SecurityConfigurer<O, B>>,後續還會用到configurers這個全局變量,這裏執行完畢,此初始化方法執行完畢!

繼續跟進,直到執行@Bean # springSecurityFilterChain(),產一個過濾器鏈,這是較爲核心的部分!

代碼以下:

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

直接執行到最後一句,經過webSecurity.build()建立過濾器鏈,跟進,調用父類AbstractSecurityBuilder#build(),根據時序圖,後面會屢次調用此類的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");
	}

繼續調用子類的AbstractConfiguredSecurityBuilder#doBuild()方法:

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

看下繼承類圖:

這四個類穿插不停的執行。。。

繼續執行AbstractConfiguredSecurityBuilder#init()方法,這裏調用的是它自身的init方法

@SuppressWarnings("unchecked")
	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);
		}
	}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
		List<SecurityConfigurer<O, B>> result = new ArrayList<SecurityConfigurer<O, B>>();
		for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
			result.addAll(configs);
		}
		return result;
	}

由前所述及時序圖可知,這裏的this.configurers已在前面初始化過,value存儲的就是自定義的MyWebSecurityConfig,因此這裏遍歷configurers,實際調用的是MyWebSecurityConfig的init類,因爲沒有重寫init類,因此這裏就調用了父類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);
			}
		});
	}

接着就調用了getHttp()

@SuppressWarnings({ "rawtypes", "unchecked" })
	protected final HttpSecurity getHttp() throws Exception {
		if (http != null) {
			return http;
		}

		DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
				.postProcess(new DefaultAuthenticationEventPublisher());
		localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
        //獲取AuthenticationManager對象,這個類在登錄的時候會用上,用來產生登錄結果:Authentication
		AuthenticationManager authenticationManager = authenticationManager();
		authenticationBuilder.parentAuthenticationManager(authenticationManager);
		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);
			}
		}
        //調用子類方法,基本就是自定義的Config類
		configure(http);
		return http;
	}

這個方法基本就是獲取各類配置了:

首先是獲取AuthenticationManager對象,這個類能夠用來生成Authentication對象,也就是登錄最終生成的結果

protected AuthenticationManager authenticationManager() throws Exception {
		if (!authenticationManagerInitialized) {
			configure(localConfigureAuthenticationBldr);
			if (disableLocalConfigureAuthenticationBldr) {
				authenticationManager = authenticationConfiguration
						.getAuthenticationManager();
			}
			else {
				authenticationManager = localConfigureAuthenticationBldr.build();
			}
			authenticationManagerInitialized = true;
		}
		return authenticationManager;
	}

這裏是經過authenticationConfiguration.getAuthenticationManager(),這裏的authenticationConfiguration是一個全局AuthenticationConfiguration對象,由Spring生成的代理對象:

它的屬性AuthenticationManager爲null,且未被初始化,調用其getAuthenticationManager()

public AuthenticationManager getAuthenticationManager() throws Exception {
		if (this.authenticationManagerInitialized) {
			return this.authenticationManager;
		}
		AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder(
				this.objectPostProcessor, this.applicationContext);
		if (this.buildingAuthenticationManager.getAndSet(true)) {
			return new AuthenticationManagerDelegator(authBuilder);
		}

		for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
			authBuilder.apply(config);
		}

		authenticationManager = authBuilder.build();

		if (authenticationManager == null) {
			authenticationManager = getAuthenticationManagerBean();
		}

		this.authenticationManagerInitialized = true;
		return authenticationManager;
	}

這裏調用authBuilder.apply(config),實際是繼續初始化AbstractConfiguredSecurityBuilder的全局變量:

LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers

該對象在上面已經初始化過了,後面調用authBuilder.build(),它繼續回調AbstractSecurityBuilder,操做的仍然是上述的configurers,比較複雜,略過

其中,在此build()方法中當調用performBuild()時:

@Override
	protected ProviderManager performBuild() throws Exception {
		if (!isConfigured()) {
			logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
			return null;
		}
		ProviderManager providerManager = new ProviderManager(authenticationProviders,
				parentAuthenticationManager);
		if (eraseCredentials != null) {
			providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
		}
		if (eventPublisher != null) {
			providerManager.setAuthenticationEventPublisher(eventPublisher);
		}
		providerManager = postProcess(providerManager);
		return providerManager;
	}

這裏就是產生ProviderManager

這裏默認的Provider就是DaoAuthenticationProvider,這裏未看到如何注入,應該也是以前初始化的吧,建立完畢調用providerManager = postProcess(providerManager),這裏調用的是spring的AutowireCapableBeanFactory,目的是將ProviderManager裝配成一個bean,最終返回此對象!

一步步返回,回到了authBuilder.build(),獲得了AuthenticationManager的實例對象:ProviderManager,這個類在表單登錄是是一個很重要的核心類!

在比對下對象authenticationConfiguration

能夠看到屬性已經不爲空,初始化狀態變爲true了

繼續返回到getHttp()方法,進行默認的HttpSecurity配置,略過,直到執行configure(http)方法,這裏實際調用的就是自定義的MyWebSecurityConfig的configure方法:

執行前,看下http對象:

屬性以下:

buildState = UNBUILT
configures = {
class org.springframework.security.config.annotation.web.configurers.CsrfConfigurer=[org.springframework.security.config.annotation.web.configurers.CsrfConfigurer@2dd8239], 
class org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer=[org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer@472698d], class org.springframework.security.config.annotation.web.configurers.HeadersConfigurer=[org.springframework.security.config.annotation.web.configurers.HeadersConfigurer@7b7683d4], 
class org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer=[org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer@40712ee9], class org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer=[org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer@2e53b094], class org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer=[org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer@39fa8ad2], 
class org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer=[org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer@76ddd61a], 
class org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer=[org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer@3f92a84e], 
class org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer=[org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer@cf67838], class org.springframework.security.config.annotation.web.configurers.LogoutConfigurer=[org.springframework.security.config.annotation.web.configurers.LogoutConfigurer@6137cf6e]
}

filter = [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@56f521c6]
RequestMatcher = org.springframework.security.web.util.matcher.AnyRequestMatcher@1

執行完畢以後返回到AbstractSecurityBuilder,init()方法執行完畢,繼續執行performBuild(),調用WebSecurity的perfotmBuild():

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

這裏首先根據securityFilterChainBuilders的大小建立一個數組,通常都只有一個過濾器鏈,因此size=1,遍歷此對象

就是HttpSecurity對象,上面羅列出的他的configurers屬性,包含的是各類Configurer對象,這些對象就是用來生成Filter對象!

遍歷調用configurer.build(),仍然是調用AbstractSecurityBuilder的build(),調用太屢次了,不深刻了,最終生成了一個securityFilterChain:

同時生成一個FilterChainProxy,過濾器代理類,後續登錄等各類請求的入口就是這個類了,能夠參見第二篇登錄流程介紹,debug開始的地方就是FilterChainProxy#doFilter,這裏真是一個典型的Filter模式實現!

至此,springSecurityFilterChain()執行完畢,返回的就是一個Filter:springSecurityFilterChain,做爲一個Bean!

生成完這個對象,初始化流程最重要的事情就完成了,後續主要就是在這個過濾器鏈中處理各類請求!

相關文章
相關標籤/搜索