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!

生成完这个对象,初始化流程最重要的事情就完成了,后续主要就是在这个过滤器链中处理各类请求!

相关文章
相关标签/搜索