前言
本文跟大家聊聊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;
}
这个也就是开头提到构造体系的核心组件之一。
- 两个核心组件的建造者
- FilterChainProxy的构造器:WebSecurity
这个相对简单一些,不准备细聊,感兴趣的可以自己看看源码。 - DefaultSecurityFilterChain的构造器:HttpSecurity
这个是我们主要配置的重点,也是复杂度爆表的。我们将重点聊这个。
- FilterChainProxy的构造器:WebSecurity
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#init
和SecurityConfigurer#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方法的核心业务流程为;
- 创建Filter
- 根据相关配置设置Filter
- 通过ObjectPostProcessor完成初始化
- 向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;
}
}
总结
-
配置体系主要包括:
SecurityBuilder<O>
、SecurityConfigurer<O, B>
-
FilterChainProxy由WebSecurity构建。
-
DefaultSecurityFilterChain由HttpSecurity构建。
-
SecurityConfigurer<O, B>
负责配置SecurityBuilder,指引SecurityBuilder如何构建。 -
HttpSecurity本质上是通过一系列的
SecurityConfigurer<O, B>
创建Filter,来构建DefaultSecurityFilterChain。 -
我们配置HttpSecurity的原理如下:
HttpSecurity ->
SecurityConfigurer<O, B>
-> Customizer本质上是通过Customizer修改SecurityConfigurer,从而改变生成的Filter的属性。
-
推荐使用基于Customizer的HttpSecurity方法进行配置。作用清晰:引入某一过滤器,配置明确:写在Customizer里面了。
后记
本文我们讨论了Spring Security的配置体系,希望大家看源码时不至于在懵逼,琢磨某个Filter时不至于迷失在源码里。下一次,我们聊聊UsernamePasswordAuthenticationFilter。相信大家期盼已久了吧。