Spring Security之配置体系

前言

本文跟大家聊聊Spring Security,看似简单易用的配置,实际上很难理解的配置体系。

配置体系概要

Spring Security的配置体系在我看来,主要分为两大类:

  • 负责构建核心组件的SecurityBuilder<O>
  • 负责对核心组件的Builder进行配置的SecurityConfigurer<O, B>

核心组件

先大致介绍一下Spring Security的核心组件,如此我们才能更好的理解相关配置。

  • FilterChainProxy
    这是个Filter。实际上是SpringSecurity的全权代理。从名字我们也能看出,他是一个FilterChain的“代理”。这里的“代理”类似于明星的经纪人,就是负责接活的人。而FilterChain是干活的。FilterChain代表的是一个基于SpringSecurity的安全过滤器链条。很明显,他是一系列Filter的组合。

    在请求到来时,FilterChainProxy先遍历所有的SecurityFilterChain,然后找到匹配的SecurityFilterChain,遍历其中的Filter并调用doFilter方法,从而实现安全过滤器的逻辑。

  • DefaultSecurityFilterChain
    这是FilterChainProxy的助手,同时也是一个完整的安全过滤器链条。他为FilterChainProxy提供了满足某一路径条件所有Filter。

这两级结构,为下面这一场景提供了可靠的安全解决方案:
应用中存在多个DispatcherServlet,分别负责不同的请求路径。然后二者负责的功能,所要求的安全性不同。例如:一个为手机端应用提供服务,面向广大市民,认证机制使用的是JWT。另一个负责的是后台管理服务,认证机制使用的是Session,面向企业管理人员。
按照两级结构,就基于两个DispatcherServlet的路径配置不同的SecurityFilterChain,交给FilterChainProxy即可。

核心组件的构造器

Spring Security抽象出一个共同的接口,两个不同的构造器来分配构造上面两个核心组件。

  • 构造器接口:
public interface SecurityBuilder<O> {
	O build() throws Exception;
}

这个也就是开头提到构造体系的核心组件之一。

  • 两个核心组件的建造者
    1. FilterChainProxy的构造器:WebSecurity
      这个相对简单一些,不准备细聊,感兴趣的可以自己看看源码。
    2. DefaultSecurityFilterChain的构造器:HttpSecurity
      这个是我们主要配置的重点,也是复杂度爆表的。我们将重点聊这个。

HttpSecurity

为了搞清楚,我们需要先从HttpSecurity的“族谱及社会关系”开始。
HttpSecurity的“族谱及社会关系”
UML中的O,是DefaultSecurityFilterChain。
B和H都指代的则是构建者,也就是HttpSecurity 。B的作用之一就是方便你链式调用。

PS: 为了不至于上来就劝退,决定由浅入深的啃图里的类/接口。

AbstractSecurityBuilder

/**
 * 基本的SecurityBuilder,主要是确保对象只构建一次。
 */
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
    @Override
 	public final O build() throws Exception {
 		// 利用AtomicBoolean,确保只构建一次
 		if (this.building.compareAndSet(false, true)) {
 			this.object = doBuild();
 			return this.object;
 		}
 		throw new AlreadyBuiltException("This object has already been built");
 	}
 	// 真正的构建方法,由继承者实现。
 	protected abstract O doBuild() throws Exception;
 }

HttpSecurityBuilder

Http请求的安全构建者。这是个接口,定义的是Http请求的安全构建者的行为。虽然目前只有一个实现类HttpSecurity。但是大家一定要注意,从设计上的职责和定义上进行区分。

public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
	extends SecurityBuilder<DefaultSecurityFilterChain> {
	// 管理configurerer:2个方法
	<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);
	<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);
	// 管理sharedObjects:2个方法
	<C> void setSharedObject(Class<C> sharedType, C object);
	<C> C getSharedObject(Class<C> sharedType);
	// 这两个方法与登录有关,也是web安全的一部分。
	H authenticationProvider(AuthenticationProvider authenticationProvider);
	H userDetailsService(UserDetailsService userDetailsService) throws Exception;
	// 添加过滤器相关
	H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
	H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);
	H addFilter(Filter filter);
}

在职责上,继承了SecurityBuilder,是DefaultSecurityFilterChain的构建者。
在定义上,引入了另一个泛型:H extends HttpSecurityBuilder<H>。第一眼是不是感觉云里雾里,不明就里?其实,我个人认为,这个HttpSecurityBuilder名字并不是很恰当的。我觉得ConfigurableSecurityBuilder或许更恰当。因为他定义的行为包括三部分:管理SecurityConfigurer、管理共享对象、过滤器相关。而以上行为的本质还是告诉建造者,如何构建DefaultSecurityFilterChain,可以理解为建造者的配置。
而H的作用是,限定行为/接口方法中的SecurityConfigurer的。而SecurityConfigurer本身又带2个泛型,等会儿我们再细聊。

SecurityConfigurer

主要用于配置SecurityBuilder,帮助SecurityBuilder构建O。

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

O是建造者B要建造的目标对象。这个类定义的意思是,我可以配置负责构造O的Builder。

init方法,可以初始化自身,也可以初始化builder(例如,提前设置一些属性)。

configurer方法,就是配置builder的重头戏了。一般来说,都是往builder里添加过滤器。

SecurityConfigurer有一个抽象类指定配置生产DefaultSecurityFilterChain的HttpSecurityBuilder。

public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
		extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {}

对应到SecurityConfigurer的泛型:
O为DefaultSecurityFilterChain, H为B。

B限定于生产DefaultSecurityFilterChain的HttpSecurityBuilder。额,还是觉得挺乱的是吧?从这里开始,复杂度就开始飙升了。系好安全带,开车!

先用一句话概括:AbstractHttpConfigurer的职责是配置生产DefaultSecurityFilterChain的HttpSecurityBuilder。按照这个定义,我们简化一下类定义,删除掉泛型H,改成下面:

public abstract class AbstractHttpConfigurer<B extends HttpSecurityBuilder>
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B>

虽然定义还是很长,但已经能理解了。此时只有一个泛型B了,而泛型B的上界是HttpSecurityBuilder。
那么原来的T到底是要干嘛呢?从定义看:T extends AbstractHttpConfigurer<T, B>;无非就是指待某个具体的AbstractHttpConfigurer呗。因此,个人认为,只有一个作用,为了方便方法调用:方便返回值或入参的类型自动转换。

AbstractConfiguredSecurityBuilder

通过SecurityConfigurer进行配置的SecurityBuilder的抽象类。

 public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
   extends AbstractSecurityBuilder<O> {
   // 记录每一个SecurityConfigurer的Class对应的所有SecurityConfigurer注册实例。
   private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
   // 共享对象。这里涉及到需要多个过滤器协作的对象。
   private final Map<Class<?>, Object> sharedObjects = new HashMap<>();
   // 为了支持对象的初始化。
   private ObjectPostProcessor<Object> objectPostProcessor;
   private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
   	// 为了节省篇幅,简单说一下功能:登记/注册configurer到this.configurers
   }

   /**
   * 核心构建方法:模板模式
    */
   @Override
   protected final O doBuild() throws Exception {
       // 同步锁
   	synchronized (this.configurers) {
   		// 初始化阶段
   		this.buildState = BuildState.INITIALIZING;
   		// 初始化前:允许子类进行初始化前的预处理。-钩子方法
   		beforeInit();
   		// 初始化:调用所有的configurers的init方法
   		init();
   		// 配置阶段
   		this.buildState = BuildState.CONFIGURING;
   		// 配置前:允许子类进行配置前的预处理。-钩子方法
   		beforeConfigure();
   		// 进行配置:调用所有的configurers的configure方法
   		configure();
   		// 构建阶段
   		this.buildState = BuildState.BUILDING;
   		// 执行构建;与其他方法不同,这是个抽象方法。因为只有具体实现类才知道要new什么对象。
   		O result = performBuild();
   		// 构建完成
   		this.buildState = BuildState.BUILT;
   		return result;
   	}
   }
}

不知道你们有没有发现,这个类的定义与前面的SecurityConfigurer在泛型上,是一模一样的!这样的安排都是为了使用SecurityConfigurer。

  • 小结一下:
    作用一:支持通过SecurityConfigurer对构建者进行配置。
    作用二:支持对构建对象的IOC初始化过程,例如:afterPropertiesSet()、各种Aware的相关方法的调用。当然也可以通过注册自定义的ObjectPostProcessor,对目标对象进行修改。
    作用三:通过模板模式,统一对象O的构建步骤/流程。基于SecurityConfigurer进行配置的构建流程。

Customizer<T>

没想到吧?还有一个编外人员。别紧张,他是我们用来修改SecurityConfigurer的。上一篇文章的demo,就是一个Customizer。他的原理/思路也十分简单,就是我把当前对象丢给你,你想配置什么,自己设置就是了。例如:

httpSecurity.csrf(csrfConfigurer -> csrfConfigurer.ignoringRequestMatchers(“/**/*.do”))`。

这意思就是,我给你传一个配置HttpSecurity的CsrfConfigurer,HttpSecurity会调用Customizer#customize方法,然后就能执行到你配置的csrfConfigurer.ignoringRequestMatchers("/**/*.do"),对路径/**/*.do不校验csrfToken。
可能有同学不熟悉lambda表达式。上面csrf方法传入的参数等价于

// CsrfConfigurer<HttpSecurity>是配置HttpSecurity的Configurer,他会引入CsrfFilter。
new Customizer<CsrfConfigurer<HttpSecurity>>() {
	public void customize(CsrfConfigurer csrfConfigurer) {
		csrfConfigurer.ignoringRequestMatchers("/**/*.do");
	}
}

HttpSecurity的详解

好了,终于到这位大爷了。

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
	private final RequestMatcherConfigurer requestMatcherConfigurer;
	private List<OrderedFilter> filters = new ArrayList<>();
	// 
	private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
	private AuthenticationManager authenticationManager;
	
	public HttpSecurity csrf(Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer) throws Exception {
		ApplicationContext context = getContext();
		// 这里调用了3个方法,依次为:
		// 1. 创建CsrfConfigurer
		// 2. 类似于CAS,如果存在,就直接返回,否则就注册。
		// 3. 调用Customizer.customize方法配置CsrfConfigurer
		csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context)));
		return HttpSecurity.this;
	}
	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
			throws Exception {
		// 从已注册的configurers中获取对应的configurer
		// 之所以会这样设计,是因为需要对HttpSecurity进行一些默认配置。后面会说。
		// 因此需要确保配置的是同一个对象。
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}
	public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
		// 设置configurer的objectPostProcessor。后面configurer在配置时,会通过他对某些对象进行初始化。如此才能完成ObjectPostProcessor的初始化工作。
		configurer.addObjectPostProcessor(this.objectPostProcessor);
		// 构建者,为了便于configurer获取共享对象。
		configurer.setBuilder((B) this);
		// 调用父类的add方法注册configurer
		add(configurer);
		return configurer;
	}
	@Override
	protected DefaultSecurityFilterChain performBuild() {
		// 对过滤器进行排序
		this.filters.sort(OrderComparator.INSTANCE);
		List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
		for (Filter filter : this.filters) {
			sortedFilters.add(((OrderedFilter) filter).filter);
		}
		// 构建DefaultSecurityFilterChain
		return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
	}
}

由于可以配置的SecurityConfigurer实在是太多了,这里挑了相对简单的CsrfConfigurer作为典型案例。具体的过程,如摘抄的源码上的注释,就不多啰嗦了。

通过SecurityConfigurer引入Filter

不知道大家有没有这样一个疑问:我们并没有看到CsrfFilter是如何添加到HttpSecurity的filters属性,只看到引入的CsrfConfigurer。而这就是,我们SecurityConfigurer最为重要的存在意义:引入Filter。
AbstractConfiguredSecurityBuilder#doBuild方法,我们知道在真正构建目标对象之前,会先后调用SecurityConfigurer#initSecurityConfigurer#configure方法。而引入Filter的密码就藏在configure方法中。

public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
		
	@Override
	public void configure(H http) {
		// 创建CsrfFilter
		CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
		RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
		if (requireCsrfProtectionMatcher != null) {
			// 指定需要防御CSRF的请求
			filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
		}
		AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
		if (accessDeniedHandler != null) {
			// 指定访问异常处理器
			filter.setAccessDeniedHandler(accessDeniedHandler);
		}
		LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
		if (logoutConfigurer != null) {
			// 登出接口也进行CSRF防御
			logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
		}
		SessionManagementConfigurer<H> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
		if (sessionConfigurer != null) {
			// 这里涉及Session管理,后面聊认证的时候再细聊
			sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());
		}
		// 通过ObjectPostProcessor完成初始化,或者修改
		filter = postProcess(filter);
		// 向HttpSecurity中添加CsrfFilter
		http.addFilter(filter);
	}
}

如源码所示,其configure方法的核心业务流程为;

  1. 创建Filter
  2. 根据相关配置设置Filter
  3. 通过ObjectPostProcessor完成初始化
  4. 向HttpSecurity中添加Filter

几乎所有与Filter相关的SecurityConfigurer都是这样对HttpSecurity实现配置的。而配置的重点也就是向HttpSecurity中添加Filter。

HttpSecurity默认配置

新版本的默认配置在HttpSecurityConfiguration

class HttpSecurityConfiguration {
	@Bean(HTTPSECURITY_BEAN_NAME)
	@Scope("prototype")
	HttpSecurity httpSecurity() throws Exception {
		LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
		// 认证管理器的建造者,会放到
		AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
				this.objectPostProcessor, passwordEncoder);
		authenticationBuilder.parentAuthenticationManager(authenticationManager());
		authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
		// 创建HttpSecurity,同时会将authenticationBuilder放入sharedObjects中
		HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
		// 异步请求管理器,需要支持获取当前请求的用户凭证相关信息
		WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
		webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
		// @formatter:off
		http
			// 引入CsrfFilter
			.csrf(withDefaults())
			.addFilter(webAsyncManagerIntegrationFilter)
			// 引入异常处理器,以便处理权限异常
			.exceptionHandling(withDefaults())
			// 引入HeaderWriterFilter,便于设置默认的安全请求头
			.headers(withDefaults())
			// 引入SessionManagementFilter,以便确保session相关功能
			.sessionManagement(withDefaults())
			// 引入SecurityContextHolderFilter,以便获取当前用户
			.securityContext(withDefaults())
			// 引入RequestCacheAwareFilter,以便恢复认证前的访问异常请求
			.requestCache(withDefaults())
			// 引入AnonymousAuthenticationFilter,管理匿名用户
			.anonymous(withDefaults())
			// 引入SecurityContextHolderAwareRequestFilter,以便基于servletApi实现认证
			.servletApi(withDefaults())
			// 引入DefaultLoginPageGeneratingFilter,以便自动生成登录页面
			.apply(new DefaultLoginPageConfigurer<>());
		// 引入LogoutFilter,以便处理默认的登出请求
		http.logout(withDefaults());
		// @formatter:on
		// 引入基于spring的spi机制引入的configurer,如果开发者没有配置的,目前应该是没有相关的configurer。
		applyDefaultConfigurers(http);
		return http;
	}
}

总结

  1. 配置体系主要包括:SecurityBuilder<O>SecurityConfigurer<O, B>

  2. FilterChainProxy由WebSecurity构建。

  3. DefaultSecurityFilterChain由HttpSecurity构建。

  4. SecurityConfigurer<O, B>负责配置SecurityBuilder,指引SecurityBuilder如何构建。

  5. HttpSecurity本质上是通过一系列的SecurityConfigurer<O, B>创建Filter,来构建DefaultSecurityFilterChain。

  6. 我们配置HttpSecurity的原理如下:

    HttpSecurity -> SecurityConfigurer<O, B> -> Customizer

    本质上是通过Customizer修改SecurityConfigurer,从而改变生成的Filter的属性。

  7. 推荐使用基于Customizer的HttpSecurity方法进行配置。作用清晰:引入某一过滤器,配置明确:写在Customizer里面了。

后记

本文我们讨论了Spring Security的配置体系,希望大家看源码时不至于在懵逼,琢磨某个Filter时不至于迷失在源码里。下一次,我们聊聊UsernamePasswordAuthenticationFilter。相信大家期盼已久了吧。

参照

Spring Security的架构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值