从源码角度拆解SpringSecurity之万能的SecurityBuilder

从源码角度拆解SpringSecurity系列文章

从源码角度拆解SpringSecurity之核心流程
从源码角度拆解SpringSecurity之万能的SecurityBuilder
从源码角度拆解SpringSecurity之勤劳的FilterChainProxy
从源码角度拆解SpringSecurity之C位的AuthenticationManager



前言

上一篇我们梳理了整个框架的核心流程,知道了SpringSecurity其实是基于Filter来实现的认证和权限管理,以及它是如何一步步被创建出来的。可是我们使用SpringSecurity框架时最特别的代码就是 “http.xxx().xxx().and().xxx()…” 了吧,这些语句其实就是我们定义的配置,那么这些配置又是如何被加载到框架中的呢,本章我们就一起来进行拆解吧。


注意

文章所摘源码版本为spring-boot-starter-security:5.0.6.RELEASEspring-security-oauth2:2.3.5.RELEASE


一、顶层配置类是怎么注入到IoC的?

上一章讲到构建Filter的配置类实际上是从IoC获取的,即通过 AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers方法来进行获取,而这个方法实质上又是从IoC中获取type为WebSecurityConfigurer.class的bean

//从IoC获取顶层配置类
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
				.getBeansOfType(WebSecurityConfigurer.class);

接下来我们回忆下平时开发时的常用配置代码吧

//开启安全配置
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	...
}

//开启认证服务配置
@Configuration
@EnableAuthorizationServer
public class EndpointConfig extends AuthorizationServerConfigurerAdapter {
	...
}

//开启资源配置
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
	...
}

第一个是开启SpringSecurity安全框架的必要配置,里面除了注入WebSecurityConfigurer之外还做了其他必要组件的初始化操作,第二个是我们需要使用SpringSecurity为我们封装Endpoint时要开启的配置,第三个是当我们需要对资源访问控制权限做控制时要开启的配置(似乎功能和第一个有重叠,甚至有些时候会造成理解歧义,故后续版本已将 @EnableResourceServer注解标记为过时

继承
实现
SecurityConfig
WebSecurityConfigurerAdapter
WebSecurityConfigurer

SecurityConfig实际上是WebSecurityConfigurer的派生类,而 @EnableWebSecurity 又包含了 @Configuration 注解

@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 {
	boolean debug() default false;
}

所以这实际上是往IoC注入了类型为WebSecurityConfigurer的bean。
再来看 @EnableAuthorizationServer,它import了 AuthorizationServerSecurityConfiguration配置类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//导入AuthorizationServerSecurityConfiguration配置类
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {

}

//又继承自WebSecurityConfigurerAdapter,如上文所说,该类是WebSecurityConfigurer的派生类
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
	...
}

所以这也相当于是往IoC注入了类型为WebSecurityConfigurer的bean。
同理 @EnableResourceServer也是以同样的逻辑进行注入的,这里就不再赘述。
所以顶层配置有哪些,实际上是取决于我们需要配置哪些,具体配置的来龙去脉这下搞清楚了吧?


二、configurers是怎么来的?

上一章我给大家留了一个小疑问,就是 AbstractConfiguredSecurityBuilder 中的 configurers 是怎么来的,接下来我们就揭晓答案。

	//new出一个空Map等待子配置的加入
	private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
	
	//都是通过add方法往里put的,这是一个私有方法,其调用链均来自apply方法
	private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
		...
		synchronized (configurers) {
			...
			this.configurers.put(clazz, configs);
			...
		}
	}
	
	public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
			throws Exception {
		...
		add(configurer);
		return configurer;
	}

	public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
		add(configurer);
		return configurer;
	}
	

所以子配置其实均来自apply方法,还记得上一章讲的WebSecurityConfiguration.setFilterChainProxySecurityConfigurer方法吗,那里面其实就将顶层配置类通过apply方法put进去的,核心代码如下所示

package org.springframework.security.config.annotation.web.configuration;
...
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
	
	...
	
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception {
		...
		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
			webSecurity.apply(webSecurityConfigurer);
		}
		...
	}

	...
	
}

通过上文我们已经知道顶层配置是类型为WebSecurityConfigurer的bean,而无一例外他们都来自派生类WebSecurityConfigurerAdapter,接下来我们看看这个类到底有何乾坤吧

@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
		WebSecurityConfigurer<WebSecurity> {
	...
	
	//如上文所说WebSecurity在执行doBuild时会依次调用顶层配置的init方法和configurer方法
	public void init(final WebSecurity web) throws Exception {
		//先调用getHttp方法生成HttpSecurity
		final HttpSecurity http = getHttp();
		...
	}

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

		...

		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方法
		configure(http);
		return http;
	}
	
	...
	
	//提供configure的默认实现,该方法一般会被我们覆盖并加入自己的配置
	protected void configure(HttpSecurity http) throws Exception {
		logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
		
		//spring的默认配置,如果不覆写(或覆写后调用super.configure)就会使用该配置
		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}
	
	...
}

这个类比较长,摘取了关键部分(似乎还是比较长 😦)。由上一章可知,WebSecuritydoBuild方法会依次调用顶层配置的init方法,而init方法会调用getHttp方法来生成HttpSecurity对象。在getHttp方法中我们又看到熟悉的http.xx().xx().and().xx()…

public final class HttpSecurity extends
		AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>,
		HttpSecurityBuilder<HttpSecurity> {
	
	...
	
	//有很多类似的方法仅拿一个来举例说明
	public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
		return getOrApply(new HttpBasicConfigurer<>());
	}
	
	...

	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
			C configurer) throws Exception {
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}

	...
	
}

原来这些方法都会将一个子配置类进行添加并返回,注意是getOrAppy说明同样的配置只可能被添加一次并且最终会调用apply方法。也就是说我们熟悉的**http.xx().xx().and().xx()…**方法其实是在添加一些列的configurer。


三、万能的SecurityBuilder

回顾上一篇,顶层配置在执行doBuild时先后会执行initconfigureperformBuild,其中前两者就是将添加各种子配置,后者就是实际构建出需要的对象,接着我们再来看看performBuild不为人知的的另一面吧。

	protected Filter performBuild() throws Exception {
		...
		//FilterChainProxy的chainSize是需要忽略的url地址数加上需要添加的子配置数
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		//逐个遍历需要忽略的url并生成相应的DefaultSecurityFilterChain
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		//逐个遍历子配置(http.xx().xx()...添加的)并生成相应的DefaultSecurityFilterChain
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			//添加时会先调用子配置的build方法(此时已初显SecurityBuilder的神威了,万物皆可build)
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		
		...

		Filter result = filterChainProxy;
		
		...
		
		return result;
	}

在通过子配置构建SecurityFilterChain时,会先调用子配置的build的方法,这时就开始套娃了,又会把走到doBuild方法,再一次把init、configurer、performBuild走一遍。
前一次的configurers是顶层配置(假如有x条),这一次的configurers是第二层配置(通过http.xx().xx()…添加的,假如有y条,会循环执行doBuild流程x次)
差不多就是这个意思
虽然它套娃了,对!明目张胆的套娃!但配方还是调整下了的,这一次的init和configurer会依次调用第二层配置的方法。
还是拿http.httpBasic() 来举例说明,由上文可知这个调用实际上是往HttpSecurity中添加了HttpBasicConfigurer子配置,我们再来看看这些子配置又长什么样吧

public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends
		AbstractHttpConfigurer<HttpBasicConfigurer<B>, B> {

	...
	
	@Override
	public void init(B http) throws Exception {
		registerDefaults(http);
	}

	...

	@Override
	public void configure(B http) throws Exception {
		AuthenticationManager authenticationManager = http
				.getSharedObject(AuthenticationManager.class);
		BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(
				authenticationManager, this.authenticationEntryPoint);
		...
		RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
		...
		basicAuthenticationFilter = postProcess(basicAuthenticationFilter);
		http.addFilter(basicAuthenticationFilter);
	}
}

init就是为构建filter进行一些初始化设置,configure则是实际构建filter并添加到HttpSecurity中。细心的观众朋友可能已经发现了,里面有个特别骚的操作http.getSharedObject,顾名思义这是从整个HttpSecurity所共享的对象池中进行取值,那这些值又是什么时候被赋值进去的呢❓

	public <C> void setSharedObject(Class<C> sharedType, C object) {
		this.sharedObjects.put(sharedType, object);
	}

一个平平无奇的方法,可是到处都有setSharedObjectgetSharedObject的调用有木有😳,而这些对象又几乎都是通过本章的主角SecurityBuilder来创建的,大致的继承/实现/引用关系图如下

实现
实现
继承
引用
引用
继承
实现
实现
WebSecurity
SecurityBuilder
HttpSecurity
HttpSecurityBuilder
HttpBasicConfigure
...
ProviderManagerBuilder
AuthenticationManagerBuilder
...

可以说SpringSecurity中的绝大部分组件都是通过SecurityBuilder创建的,如FilterChainProxy、DefaultSecurityFilterChain、AuthenticationManagerBuilder、AuthenticationManager、ProviderManager…以一己之力撑起了整片天空有木有 😃


简单总结本章内容,介绍了顶层配置、第二层配置的由来,这些配置是如何被套娃式使用的,以及通过setSharedObject方法管中窥豹见识了SecurityBuilder的强大之处。
至此我们已经将SpringSecurity的启动流程拆解完毕,下一篇我们将对框架的拦截部分勤劳的FilterChainProxy展开说明。
怎么样,你看懂了吗,有问题或者发现文章当中有谬误的地方,都欢迎留言评论哦。

转载请注明出处:从源码角度拆解SpringSecurity之万能的SecurityBuilder

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值