前言
本文是对 Spring Security Core 4.0.4 Release 进行源码分析的系列文章之一;
本系列开始,将讲解有关 Spring Security 的配置相关的内容;
本文为作者的原创作品,转载需注明出处;
简介
在《Spring Security 源码分析九:Java config - WebSecurity & @EnableWebSecurity》文中,笔者在 HttpSecurity 小节中留了一个悬念,那就是通过 HttpSecurity 的 build() 方法将返回一个 SecurityFilterChain 对象,SecurityFilterChain 对象由多个 Filter 所构成,组成了一条 Spring Security 的过滤链,但是并没有深入的去探讨 HttpSecurity 是如何去构建 SecurityFilterChain 对象的内部机制;本篇文章,笔者将试图去回答这个问题;
配置用例
先来看一些简单的用例,
单个 FilterChain 的配置
1 | protected void configure(HttpSecurity http) throws Exception { |
等价于做了如下的配置
1 | <http> |
- 每一个 and() 操作符就相当于 XML 配置中的一次换行,XML 配置中的每一行代表一个完整的配置;
配置 .authorizeRequests().anyRequest().authenticated() 相当于
1
<intercept-url pattern="/**" access="authenticated"/>
authorizeRequests() 表示开始一个 <intercept-url> 配置节点的开始;
anyRequest() 表示配置属性 pattern=”/**“;
authenticated() 表示配置属性 access=”authenticated”;
上述的配置等价于配置了一个 SecurityFilterChain 对象,既是一个 Spring Security 安全链,因为没有指定安全链的起始链接,所以该安全链默认作用在 /** 访问 request 上的,并且使用默认实现的 form login 和 http basic authentication 对象;
多个 FilterChain 的配置
在 Spring Security 源码分析八:Spring Security 过滤链二 - Demo 例子 的例子中,通过 Java Config 的方式配置了两条不同的 Security Filter Chain;
首先,通过如下的方式配置了一条 /web/** 的安全过滤链,
1 | http.antMatcher("/web/**") // the filter chain defined for web request |
再次,通过如下的方式配置了另一条 /rest/** 的安全过滤链,
1 | http.antMatcher("/rest/**") // the filter chain defined for web request; |
上述的配置等价如下的配置,
1 | <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy"> |
相关配置说明和其它配置元素
还是以上述单个 FilterChain 的配置为例
http -> HttpSecurity
Java Config 中的配置元素 http 等价于 XML 配置元素中的 <http>;该实例既表示 HttpSecurity 实例本身;
and() : HttpSecurity
Java Config 中的配置元素 and() 等价于 XML 配置元素中的换行
操作,也就表示准备要开始配置另外一个 Security 元素了;其实在 Java Config 的过程中,每一次的 and() 操作以后,返回的都是当前 HttpSecuirty 实例;可以看到,每一次相关方法的调用,比如 authorizeRequests()、formLogin()、httpBasic() 返回的都是一个 SecurityConfigurer 对象,而通过调用 SecurityConfigurer.and() 返回的永远都是 SecurityBuilder 对象,这里对应的既是 HttpSecurity;
该部分将会在后面的类图部分有更详细的介绍;
antMatcher(String antPattern) : HttpSecurity
Java Config 中的配置元素 antMatcher(antPattern) 类似于进行如下的 XML 配置,
1 | <sec:filter-chain pattern="/web/**" ... > |
开启了一个新的 Security Chain 的配置;
authorizeRequests() : ExpressionUrlAuthorizationConfigurer
Java Config 中的配置元素 authorizeRequests() 等价于 XML 配置元素中的 <intercept-url> 操作,表示对特定 URL 执行的特定操作;这个方法比较有意思,返回的是 ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry 一个内嵌对象;该部分将会在后面的类图部分有更详细的介绍;
formLogin() : FormLoginConfigurer
Java Config 中的配置元素 formLogin() 等价于 XML 配置元素中的 <form-login> 操作,表示对特定 URL 执行的特定操作;该方法返回的是 FormLoginConfigurer 对象,该部分将会在后面的类图部分有更详细的介绍;
httpBasic() : HttpBasicConfigurer
Java Config 中的配置元素 httpBasic() 等价于 XML 配置元素中的 <http-basic> 操作;该方法返回的是 HttpBasicConfigurer 对象,该部分将会在后面的类图部分有更详细的介绍;
sessionManagement() : SessionManagementConfigurer
Java Config 中的配置元素 sessionManagement() 类似于进行如下的 XML 配置,
1 | <session-management> |
该方法返回的是 SessionManagementConfigurer 对象,该部分将会在后面的类图部分有更详细的介绍;
authenticationProvider( authenticationProvider ) : HttpSecurity
Java Config 中的配置元素 authenticationProvider( authenticationProvider ) 类似于进行如下的 XML 配置,
1 | <authentication-manager> |
该方法返回的是当前的 HttpSecurity 对象;
userDetailsService( userDetailsService ) : HttpSecurity
Java Config 中的配置元素 userDetailsService() 类似于进行如下的 XML 的配置,
1 | <beans:bean id="myUserDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> |
portMapper() : PortMapperConfigurer
Java Config 中的配置元素 portMapper() 类似于进行如下的 XML 配置,
1 | <port-mappings> |
exceptionHandling() : ExceptionHandlingConfigurer
Java Config 中的配置元素 exceptionHandling() 类似于进行如下的 XML 的配置,
1 | <bean id="exceptionTranslationFilter" |
从 ExceptionHandlingConfigurer 的配置方法 configure() 中也可以看到这个逻辑,
1 |
|
其它配置
如图,展示了 HttpSecuirty 所有相关的配置项,里面还包含 csrf(),logout(),anonymous(),requiresChannel() 等等非常重要的一系列的配置方法;
References
有关 XML 的配置参考 https://docs.spring.io/spring-security/site/docs/3.0.x/reference/ns-config.html
HttpSecurity
SecurityBuilder 的实体类总共有三个,HttpSecurity、WebSecurity 以及 AuthenticationManagerBuilder;下面笔者将逐个的分析每一个相关的类;
SecurityBuilder
SecurityBuilder 接口非常的简单,提供了一个 build() 方法,顾名思义,主要是用来执行构建操作的;
1 | public interface SecurityBuilder<O> { |
构建完成以后,将会返回对象 O;
AbstractSecurityBuilder
1 | public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> { |
三个方法,
build()
执行构建动作,通过调用抽象方法 build() 完成构建;getObject()
获得构建成功以后的对象;doBuild()
抽象方法,供子类实现其构建方法;
AbstractConfiguredSecurityBuilder
该抽象类中的逻辑非常的关键了,将其核心关键代码摘要如下,
1 | public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> |
好些个核心的方法,下面我们一个一个的来屡吧,
apply(C configurer)
这里笔者罗列了两个 apply(C configurer) 方法,目的是为了描述一个调试的 bug,HttpSecurity 的 getOrApply(configurer) 方法,1
2
3
4
5
6
7
8
9"unchecked") (
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);
}如果通过 COMMAND + 鼠标左键点击 apply(configurer) 跳转到的是上述的方法
1
2
3
4public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}如果是这样的话,就有一个疑问了,那就是如果通过 configurer.and() 方法获得 HttpSecurity 既是上述的 Build 对象 B,比如通过 http.csrf().and() 就无法获取 HttpSecurity 对象了,自然也就没有办法继续进行 HttpSecurity 的相关配置了;不过,经过笔者的调试过程中发现,实际上,经过 HttpSecurity.getOrApply(configurer) 方法调用的其实是
1
2
3
4
5
6
7
8"unchecked") (
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
throws Exception {
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
add(configurer);
return configurer;
}该方法中,通过
configurer.setBuilder((B) this)
的方式将 HttpSecurity 赋值给了当前的 configurer,所以自然也就可以通过该 configurer 获取到当前的 HttpSecurity 对象了;Debug 过程的截图如下,可见,调用的正式上述方法;add(C configurer)
该方法在 apply(C configurer) 方法中被调用,目的是将 configurer 添加到 builder 的 configurers 中;唯一需要注意的是,configurers 是一个以 configurer class 为主键
的 Map,其值存储的就是按照 class 分类号的 configurers;为什么这样设计?难不成同一个类型的 configurer class 还会有多个 configurers?笔者暂时还没有想到相应的场景;setSharedObject(Class
sharedType, C object)
当需要覆盖掉 configurer 的一些工具累的时候,可以使用这个方法;doBuild()
:O
该方法是核心中的核心,执行构建完以后,将返回构建好的对象;笔者在前面分析 WebSecurity 的构建过程中比较详细的描述了 WebSecurity 的三步构造过程,init、configure 以及 perform build 步骤;现在,我们再从源码的层面,依次看下这三个方法所执行的逻辑,init build process
对应前三行代码,1
2
3buildState = BuildState.INITIALIZING;
beforeInit();
init();首先,执行 beforeInit() 方法,对应执行的是一个模板方法,子类需要继承并实现该方法来实现一些初始化之前的方法;如下所述,
1
2
3
4
5
6
7/**
* Invoked prior to invoking each {@link SecurityConfigurer#init(SecurityBuilder)}
* method. Subclasses may override this method to hook into the lifecycle without
* using a {@link SecurityConfigurer}.
*/
protected void beforeInit() throws Exception {
}再次,执行 init() 方法,
1
2
3
4
5
6
7
8
9
10
11"unchecked") (
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
....
}可以看到,在初始化构建过程当中,会首先依次执行各个 configurer 的 init() 方法;
configure build process
这里的逻辑与 init build process 大同小异,同样是先执行模板方法 beforeConfigurer() 方法,需要子类继承实现;然后再依次遍历 configurer 并执行其 configurer() 方法;perform build process
这个过程执行抽象方法 performBuild(),该方法必须由子类实现;也就是说,三个子类 HttpSecurity、WebSecurity 以及 AuthenticationManagerBuilder 都有实现这个方法,这里,我们看看 HttpSecurity 的实现,1
2
3
4
5
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparitor);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}首先,根据 filters 的优先级排好序,然后通过构造好的 requestMatcher 和 filters 来初始化一个 DefaultSecurityFilterChain 对象并返回;
init()
由 doBuild() 方法的 init build process 所调用;configure()
由 doBuild() 方法的 configure build process 所调用;
HttpSecurity
由类图可知,HttpSecurity 是一个 SecurityBuilder 实例,通过其父类 AbstractConfiguredSecurityBuilder doBuild() 方法的三步构建 init、configure 以及 perform build 最终返回一个 SecurityFilterChain 实例;可见,HttpSecurity 的职责就是通过一系列的 configurer 并且通过三步构建步骤最后生成一个 SecurityFilterChain 实例,该实例最终由 FilterChainProxy 加载;更多有关 HttpSecurity 构建的过程参考 HttpSecurity.build 部分内容;
从 HttpSecurity 相关配置元素和操作中,可以知道,HttpSecurity 通过各种配置方法注入了不同的 configurers,既 SecurityConfigurer 对象,并且通过 getOrApply(configurer) 方法将 configurer 注入到父类的 configurers 队列中(注意是个 Map 对象),
1 | "unchecked") ( |
更多有关此部分的内容参考 AbstractConfiguredSecurityBuilder 中 apply(C configurer) 小节;另外这里 apply(configurer) 调用的是父类的第一个 apply(C configurer);注意
,正是因为在 apply 的过程中,将当前的 builder 既 HttpSecurity 对象注入到了 configurer 中,所以,可以通过任意的一个 configurer 对象通过调用其 and() 方法都可以获取到当前的 HttpSecurity 对象;就类似于在配置过程中通过 http.csrf().and() 既可以获取得到 HttpSecurity 对象;
Ok,通过上述分析,我们可以知道,HttpSecurity 通过若干 SecurityConfigurer 配置,通过其 configure() 方法,创建了若干个 Filters,正是这些 Filters 构成了 HttpSecurity 的核心,也正是这些 Filters 构建了 SecurityFilterChain 对象;那么,下面,我们就来一睹 Securityconfigurer 的风采;
SecurityConfigurer
由类图可知,SecurityConfigurer 主要分为两大类,WebSecurityConfigurer 和 SecurityConfigureAdapter;
注意,这里有一个关键的行为区别,那就是,WebSecurityConfigurerAdapter 类型的配置类,比如 WebConfigSecurity 和 RestConfigSecurity 不包含与之关联的 SecurityBuilder 实例,这里指的是 WebSecurity;而 SecurityConfigurerAdapter 类型的相关配置类则相反,均包含与其关联的 SecurityBuilder 类型实例,比如 AuthenticationManagerBuilder 以及 HttpSecurity,且是一对一的关系,这也就是为什么通过 SecurityConfigurerAdapter.and() 方法可以获取与之关联的 SecurityBuilder 的根本原因;
WebSecurityConfigurer
该部分参考 WebSecurity 章节的相关内容 WebSecurityConfigurer 的介绍;WebSecurityConfigurer 的主要职责就是扩展出用户自定义的有关 HttpSecurity 的配置;
SecurityConfigurerAdapter
由上述类图可知,包含三个子类,不过这里,笔者主要针对 UserDetailsAwareConfigurer 和 AbstractHttpConfigurer 来进行讲解;并且重要的是,该类提供了两个比较重要的方法;
and()
该方法将会始终返回 HttpSecurity 对象;getBuilder()
and() 方法改回调用此方法来获取 Builder 对象,既 HttpSecurity 对象;
AbstractHttpConfigurer
由此类扩展出了多个与 HttpSecurity 相关联的 configurers,比如 HttpBasicConfigurer、FormLoginConfigurer 以及 ExpressionUrlAuthorizationConfigurer;
再来看一下类图,关注点聚焦到 HttpSecurity 上;
从 HttpSecurity.build 的介绍可知 HttpSecurity 的构建的目的就是生成 Filters,然后将这些 Filters 作为构造参数初始化 DefaultSecurityFilterChain 对象并返回;而过程中,Filters 的生成就与 configurers 的方法 configure() 息息相关了;下面,笔者就相关的方法介绍如下,
HttpBasicConfigurer.configure(B http)
参数 B 指的是泛型 Builder,这里既是 HttpSecurity;
1 |
|
下面笔者就 HttpBasicConfigurer 的配置的过程做进行详细的分析,
- 代码 3 ~ 4 行,从
SharedObject
中获取得到 AuthenticationManager 对象; - 代码 5 ~ 6 行,生成 BasicAuthenticationFilter 对象,
Filter
; - 代码 11 ~ 14 行,从 SharedObject 中获取得到 RememberMeServices 对象,并注入到 BasicAuthenticationFilter 对象中;
- 代码 16 行,将 BasicAuthenticationFilter 加入到 HttpSecurity 实例中;
ExpressionUrlAuthorizationConfigurer
该对象的 configure 方法在其父类 AbstractInterceptUrlConfigurer 中,
1 |
|
可见,目的正是为了生成一个 FilterSecurityInterceptor 既 Filter 对象,并将其载入到 HttpSecurity 对象中;
FormLoginConfigurer
同样,该对象的 configure 方法也是在其父类 AbstractAuthenticationFilterConfigurer 中;
1 |
|
上面代码的核心在 authFilter,而该 authFilter 正是 UsernamePasswordAuthenticationFilter,该 authFilter 的初始化过程在 FormLoginConfigurer 的构造函数中,
1 | public FormLoginConfigurer() { |
UserDetailsAwareConfigurer
UserDetailsAwareConfigurer 主要涉及两个实体类 InMemoryUserDetailsManagerConfigurer 和 JdbcUserDetailsManagerConfigurer;顾名思义,这两个类都是对用户身份信息进行验证的类;如类图中所示,这两个 configurers 将会作为 AuthenticationManagerBuilder 的 configures 来进行构建,最终构建的结果是返回一个 ProviderManager 对象;有关这部分内容笔者将会在下一章节对其进行描述;
总结
正如对 WebSecurity 的总结那样,WebSecurity 与 HttpSecurity 的关系是一对多的关系,也就是说,HttpSecurity 正是为 Spring Security 构建多个安全链 SecurityFilterChain 的核心类;WebSecurity 将构建出 FilterChainProxy, FilterChainProxy 包含多个 SecurityFilterChain 既安全链;