Security配置生效源码分析

Posted jazon@

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Security配置生效源码分析相关的知识,希望对你有一定的参考价值。

文章概述

本文主要解决几个问题:

  • security配置生效的入口是哪里
  • 我们平时写的security配置是如何被调用的
  • 为什么我们写的配置需要继承WebSecurityConfigurerAdapter
  • HttpSecurity和WebSecurity是干嘛用的

配置核心类

  • 启动Security,需要在启动类上添加@EnableSecurity注解
@SpringBootApplication
@EnableWebSecurity
public class ThingsboardApp 
    ...

  • @EnableSecurity解析
@Import( WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class,
		OAuth2ImportSelector.class )
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity 

	/**
	 * Controls debugging support for Spring Security. Default is false.
	 * @return if true, enables debug support with Spring Security
	 */
	boolean debug() default false;

@EnableSecurity注解通过@Import引入了WebSecurityConfiguration作为bean。

  • WebSecurityConfiguration类,是一个bean,spring启动后,关于security的配置生效从这里开始。
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware 
    ...

实现了ImportAware接口,ImportAware接口作用是将引入了@Import注解的类的AnnotationMetadata信息通过setImportMetadata方法传入ImportAware的实现类中。借鉴点:@Import是叠加在@EnableSecurity注解上的,而@EnableSecurity又是注解在启动类上的,于是在WebSecurityConfiguration类就能拿到启动类上的注解信息,通常关心的是@EnableXXXX注解的信息,因为这里可能包含一些开启的配置信息。
实现了BeanClassLoaderAware接口,将Spring容器的BeanClassLoader设置到属性中,目前没看到有用到这个属性。

配置注入

WebSecurityConfiguration类

@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception 
        ...

方法参数利用SPEL将注入类型为WebSecurityConfigurer.class的bean,WebSecurityConfigurer的重要实现类WebSecurityConfigurerAdapter,是我们配置security通常需要继承的基类。于是这里拿到了我们的配置bean,将它们设置到webSecurityConfigurers属性中。

配置生效入口

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception 
    ...
    return webSecurity.build();

这个方法返回了一个Filter的bean,即为Sevlet加入了一个过滤器,我们从之前的Security整体架构文章知道Security本质上就一个Filter,所以我们的重点就关注这个Filter是怎么生成,以及它里边有哪些属性。

WebSecurity–生效配置的对象

一个应用应当只有一个WebSecurity即可,它用于构建Security的ServletFitler,即下图红色框起来Fitler, 所以WebSecurity是一个builder。

看一下类定义

public final class WebSecurity extends
		AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
		SecurityBuilder<Filter>, ApplicationContextAware 

  • 继承类AbstractConfiguredSecurityBuilder,从字面含义来看,这个类是可配置的Builder,意思是配置Builder参数来个性化地build对象。
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
		extends AbstractSecurityBuilder<O> 
        ...

AbstractConfiguredSecurityBuilder继承自AbstractSecurityBuilder,AbstractSecurityBuilder是SecurityBuilder的抽象实现类,它相当于模板类,定义了build的流程及提供了一些公共方法。借鉴点:AbstractConfiguredSecurityBuilder是在AbstractSecurityBuilder基础上叠加了可配置的概念,所以将他们抽象成两个类。AbstractConfiguredSecurityBuilder实现可配置的方式是往builder内加入SecurityConfigurer对象(前面说的配置注入即实现了此功能),在build过程中,会将配置应用到builder上。

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> 
	void init(B builder) throws Exception;

	void configure(B builder) throws Exception;

SecurityConfigurer接口定义了两个方法,都是可用于配置builder的。
到此,我们知道了WebSecurity对象里有很多SecurityConfigurer对象,SecurityConfigurer对象就是我们日常开发时写的那些配置类,前文配置注入也提到了。

开始生效配置并创建Filter

protected final O doBuild() throws Exception 
		synchronized (configurers) 
			buildState = BuildState.INITIALIZING;

			beforeInit();
            // 这一步会调用所有SecurityConfigurer类的init方法
			init();

			buildState = BuildState.CONFIGURING;

			beforeConfigure();
            // 循环调用SecurityConfigurer类的configure方法
			configure();

			buildState = BuildState.BUILDING;

            // build方法,真正创建
			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		
	
init方法

我们重点关注WebSecurityConfigurerAdapter这个配置类的,看看它的init方法

public void init(final WebSecurity web) throws Exception 
		final HttpSecurity http = getHttp();
		web.addSecurityFilterChainBuilder(http).postBuildAction(() -> 
			FilterSecurityInterceptor securityInterceptor = http
					.getSharedObject(FilterSecurityInterceptor.class);
			web.securityInterceptor(securityInterceptor);
		);
	

可以看到这一步有个getHttp,我们平时写的配置类的方法configure(HttpSecurity http)就是在这一步被调用到的。

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

		DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
				.postProcess(new DefaultAuthenticationEventPublisher());
		localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

		AuthenticationManager authenticationManager = authenticationManager();
		authenticationBuilder.parentAuthenticationManager(authenticationManager);
		authenticationBuilder.authenticationEventPublisher(eventPublisher);
		Map<Class<?>, 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);
			
		
        // 调用我们写的配置类的configure(http)
		configure(http);
		return http;
	

可以看到首先创建了一个HttpSecurity对象,然后对它进行.csrf()等各种方法调用,我们点开方法后方法,其实.csrf()等各种方法就是往HttpSecurity对象中加入各种SecurityConfigurer,最后再调用configure(http), 这个就是我们写的配置类的configure(HttpSecurity http)方法。从命名上,我们可以推测出HttpSecurity也是一个Builder,那么HttpSecurity方法build的是什么呢?这里先不分析,继续往后看。
拿到一个HttpSecurity对象后,就调用WebSecurity.addSecurityFilterChainBuidler往里添加httpSecurity对象。
稍微总结下,WebSecurity里有很多Configurer对象,这些对象是我们平时开发时写的Security配置类,每个配置类对象都会调用init方法,创建HttpSecurity对象,并加入到WebSecurity的filterChain列表中。HttpSecurity也是一个Builder,内部也有很多Configurer对象,在getHttp()中会去调用我们写的配置类的configure(HttpSecurity http),往其中加入Configurer对象。

configure方法

init方法分析完了,接下来是configure方法,configure方法即调用我们写的配置类的configure(WebSecurity webSecurity)方法,这个方法就是直接配置的WebSecurity的某些属性。

performBuild方法

configure方法执行完后,就是performBuild方法,这个方法就是最终生成Filter的方法。我们知道,security的架构中,整个security是一个ServletFilter,在这个filter内有很多filterchain,当一个http请求进来后,会经过总filter,然后进入到筛选filterchain的过程,选中后,执行fitlerchain内的所有SecurityFilter。

在前文,我们留了个问题: HttpSecurity的build方法是生成什么的,下面我们来看看WebSecurity的perfomBuild()方法,从中寻找答案。

protected Filter performBuild() throws Exception 
        ....
            
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) 
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		
       // 可以看到这里调用了securityFilterChainBuilder.build(),这里很重要
		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;
		postBuildAction.run();
		return result;
	

第12行代码,循环了securityFilterChainBuilders,我们前文将HttpSecurity加入到此列表中,也就是这里调用的即HttpSecurity.build,所以HttpSecurity是创建SecurityFilterChain对象的,结合上面的架构图,我们可以分析出,一个HttpSecurity就对应一个SecurityFilterChain,前文我们说过,WebSecurityConfigurer配置类的init()会调用getHttp(),生成HttpSecurity对象,于是我们推出,我们写的每个配置类最终都会转变为一个SecurityFilterChain。其实从HttpSecurity的类继承关系我们也可以知道,它是用来创建SecurityFilterChain的。
FilterChainProxy将securityFilterChains作为构造参数构建对象,并返回给到最外层的方法,FilterChainProxy就是我们Security创建的ServletFilter了。
至此,security使用配置,最终创建Filter的过程就分析完毕。

借鉴点总结

  • @Import是叠加在@EnableSecurity注解上的,而@EnableSecurity又是注解在启动类上的,于是在WebSecurityConfiguration类就能拿到启动类上的注解信息,通常关心的是@EnableXXXX注解的信息,因为这里可能包含一些开启的配置信息。
  • 类定义的设计上,假设有可配置等功能,可以将其抽象出单独的类,继承自基类,参考有AbstractConfiguredSecurityBuilder和SecurityBuilder,最终将不同功能的类再从类继承关系上结合到一起,使得类名清晰,层次分明。
  • Builder和Configurer的关系,利用泛型表明Builder是创建什么的,Configurer是配置什么的,Builder对象组合了Configurer对象,使得Configurer可以配置Builder。A类功能能够影响B类,可以用的此种方法。

以上是关于Security配置生效源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Security配置生效源码分析

Spring实战----Security4.1.3鉴权之美--基于投票的AccessDecisionManager实现及源码分析

Spring Security源码:权限访问控制是如何做到的?

Spring实战----Security4.1.3认证过程源码分析

Spring Security源码:权限访问控制是如何做到的?

Django的settings文件部分源码分析