Spring Security 源码分析九:Java config - WebSecurity & @EnableWebSecurity

前言

本文是对 Spring Security Core 4.0.4 Release 进行源码分析的系列文章之一;

本系列开始,将讲解有关 Spring Security 的配置相关的内容;

本文为作者的原创作品,转载需注明出处;

简介

本博文将继续使用 Spring Security 源码分析八:Spring Security 过滤链二 - Demo 例子中所使用到的例子,来讲解,基于 Spring Boot 的 Java Config 的方式;

@EnableWebSecurity 是用户自定义 Spring Security 过滤链的入口,是核心,任何相关的认证操作,都将从这里开始;所以,笔者首先从这里入手,开启讲解 Spring Security 配置的相关内容;

配置

重温一下之前在 DemoApplication 中所介绍的一个例子,可以看到该例子继承自 WebSecurityConfigurerAdapter 并且添加了 @EnableWebSecurity 注解;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Configuration
@EnableWebSecurity
@Order(1)
static class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("manager").password("password").roles("MANAGER");

}

@Override
protected void configure(HttpSecurity http) throws Exception {

http.antMatcher("/web/**") // the filter chain defined for web request
.authorizeRequests()
.antMatchers("/web/report/**").hasRole("MANAGER")
.anyRequest().authenticated()
.and()
.formLogin()
// login 的相对路径必须与 security chain 的的相对路径吻合,这里是 /web/**;注意 login 分两步,一步是 Getter 会到 login.html,另外一步是从 login.html -> post -> /web/login/
.loginPage("/web/login")
// 允许访问
.permitAll();

}
}

@EnableWebSecurity

首先来看一下该 annotation 的注解,

Add this annotation to an @Configuration class to have the Spring Security configuration defined in any WebSecurityConfigurer or more likely by extending the WebSecurityConfigurerAdapter base class and overriding individual methods:

可以在任何通过@Configuration注解的WebSecurityConfigurerWebSecurityConfigurerAdapter类中进行 Spring Security 相关的配置;这个也正是我们上面的例子中所做的那样,但是,问题是,@EnableWebSecurity 的核心功能是什么?它的底层运作机制是什么?先来看看它的源码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class, ObjectPostProcessorConfiguration.class, SpringWebMvcImportSelector.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;
}

可以看到,该注解通过@Import导入了其它三个配置类,WebSecurityConfiguration.class、ObjectPostProcessorConfiguration.class 以及 SpringWebMvcImportSelector.class;这里最重要的是WebSecurityConfiguration.class,那么它的内部运行机制是什么呢?

WebSecurityConfiguration.class

先看看该类的说明,

Uses a WebSecurity to create the FilterChainProxy that performs the web based security for Spring Security.

It then exports the necessary beans. Customizations can be made to WebSecurity by extending WebSecurityConfigurerAdapter and exposing it as a Configuration or implementing WebSecurityConfigurer and exposing it as a Configuration. This configuration is imported when using EnableWebSecurity.

先总结一下它的功能,一句话,根据用户所配置的 Spring Security 配置通过 WebSecurity 创建出对应 FilterChainProxy 并生成相应的 Spring Security 过滤链;那么看看它是如何一步一步做到的呢,在讲解之前,要知道 WebSecurityConfiguration 是通过 @Configuration 进行注解的;

1
2
3
4
5
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {

...
}

加载用户自定义配置类

webSecurityConfigurers

如何加载用户的自定义配置呢?比如上面所介绍的 WebSecurityConfig 以及 DemoApplication 中所介绍的 RestSecurityConfig 的呢?答案就在 WebSecurityConfiguration 的成员变量webSecurityConfigurers上;

  • 首先,该方法是通过 @Autowired 注解的,也就是说在 Spring 容器初始化的时候,该方法即可被加载;在该方法的加载过程当中,通过 @Value 注解从 Spring 容器中取得 autowiredWebSecurityConfigurersIgnoreParents Spring bean 实例,并从该实例中通过方法 getWebSecurityConfigurers() 获取得到webSecurityConfigurers参数,该参数保存的既是 WebSecurityConfigRestSecurityConfig 两个配置类,既是用户通过 @EnableWebSecurity 注解自定义实现的两个有关 Spring Security 配置类,非常之关键;Wow,这时一个神奇的参数,是的,它的确是;但问题是,该参数是如何获取得到如此重要的配置类信息的呢?关键就在于 autowiredWebSecurityConfigurersIgnoreParents 所对应的 Spring Bean,来看下面这个方法;

    1
    2
    3
    4
    5
    @Bean
    public AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
    ConfigurableListableBeanFactory beanFactory) {
    return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
    }

    该方法通过注解 @Bean 初始化得到一个名为 autowiredWebSecurityConfigurersIgnoreParents 由 Spring 容器所管理的 bean;该 bean 是通过初始化 AutowiredWebSecurityConfigurersIgnoreParents 所的到的,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    final class AutowiredWebSecurityConfigurersIgnoreParents {

    private final ConfigurableListableBeanFactory beanFactory;

    public AutowiredWebSecurityConfigurersIgnoreParents(
    ConfigurableListableBeanFactory beanFactory) {
    Assert.notNull(beanFactory, "beanFactory cannot be null");
    this.beanFactory = beanFactory;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
    List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
    Map<String, WebSecurityConfigurer> beansOfType = beanFactory
    .getBeansOfType(WebSecurityConfigurer.class);
    for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
    webSecurityConfigurers.add(entry.getValue());
    }
    return webSecurityConfigurers;
    }
    }

    该类非常的简单,核心既是上面的方法 getWebSecurityConfigurers(),直接从当前的 Spring 容器中去加载类型为 WebSecurityConfigurer 类的实例;回头看看 DemoApplication 中所实现的 WebSecurityConfigRestSecurityConfig 正好继承自 WebSecurityConfigurer,所以这两个由用户自定义的 Spring Security 配置类都将在这里被加载;

  • 然后依次遍历 webSecurityConfigurer 配置并将其通过 WebSecurity 的 apply() 方法加载入WebSecurity对象实例中;为后续的构建进行准备;

创建 FilterChainProxy 实例 ( named “springSecurityFilterChain” )

由前面的系列文章分析可知,FilterChainProxy 在 Spring 容器中的 bean 的名字为 springSecurityFilterChain,该实例包含 1 个或者多个 SecurityFilterChain,并且该实例被 DelegatingFilterProxy 实例所代理,接收并处理由其所转发的请求;那么本章节将探讨的既是 FilterChainProxy 实例是如何被创建的?

答案就在 WebSecurity 中;

WebSecurity.build

首选,看看 WebSecurity 相关的注解,

The WebSecurity is created by WebSecurityConfiguration to create the FilterChainProxy known as the Spring Security Filter Chain (springSecurityFilterChain). The springSecurityFilterChain is the Filter that the DelegatingFilterProxy delegates to.

Customizations to the WebSecurity can be made by creating a WebSecurityConfigurer or more likely by overriding WebSecurityConfigurerAdapter.

从其注解可知,WebSecurity 的核心功能既是去创建FilterChainProxy;下面看一下 WebSecurity 构建的入口,

可见通过方法上的注解 @Bean 来构建一个名为 springSecurityFilterChain 的 Spring Bean,该 bean 就是 FilterChainProxy

由上一小节的第二点分析可知,当通过 webSecurityConfigurers 获取得到用户自定义配置类以后,将依次的通过调用WebSecurityapply() 将其加载;其目的其实就是为了后续的构建动作,下面,笔者就来分析一下 WebSecurity 的构建行为;

上面这段代码显示了其核心的构建步骤,其构建的步骤由这样三个流程 initconfigure 以及 perform build 所构成;将 debug 断点打在 WebSecurityConfig.configure 的方法中,来分析下面这三种情况;

init process

在开始执行该流程以前,build state 将会被置为 INITIALIZING 的状态;

从上述的调用流程中可以清晰的看到,WebSecurityConfig既用户自定义的 Spring Security 配置类的 configure(HttpSecurity) 方法将会被调用,同样,用户自定义的RestSecurityConfigconfigure(HttpSecurity) 方法同样会被调用;可以看到这两个类都被 Cglib 生成了相应的代理类,然后,调用 WebSecurityConfigurerAdapter.getHttp() 方法,该方法中最终调用到 WebSecurityConfig.configure(HttpSecurity) 方法加载用户自定义的 HttpSecurity 的配置;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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);
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
localConfigureAuthenticationBldr.getSharedObjects());
http.setSharedObject(UserDetailsService.class, userDetailsService());
http.setSharedObject(ApplicationContext.class, context);
http.setSharedObject(ContentNegotiationStrategy.class, contentNegotiationStrategy);
http.setSharedObject(AuthenticationTrustResolver.class, trustResolver);
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<HttpSecurity>()).and()
.logout();
// @formatter:on
}
configure(http);
return http;
}

可见,该方法中会首先初始化一个 HttpSecurity 对象实例,然后通过模板方法 configure(HttpSecurity) 去加载用户自定义的 Java Config Security 相关的配置;所以,这里比较迷惑人的是,照理说,init 应该只取调用 init 相关的内容,但这里确实却 调用了用户的 configure 相关的内容

因此,加载用户通过 WebSecurityConfig 和 RestSecurityConfig 自定义的有关 HttpSecurity 的配置,这里的配置包括为 HttpSecurity 配置相关的 SecurityConfigurers,以及 intercept-url 等;
configure process

执行之前,build state 将会被置为 configuring;该方法调用的是 abstract 方法 WebSecurityConfigurerAdapter.configure(WebSecurity web),也就是说,如果 WebSecurityConfigRestSecurityConfig 实现了 configure(WebSecurity web) 模板方法,将会在这里被调用;当然,显而易见,这里的目的是给用户一个机会去重载 WebSecurity 的机会;

perform build process

因为 Cglib 代理类的原因,因此 debug 断点需要打在 WebSecurity.performBuild() 方法中,否则断点不会进行;

该过程最核心的两个地方就是由箭头所指向的地方,下面笔者将来分别就这两个关键地方进行分析,

第一个地方,

securityFilterChainBuilder 实现了 SecurityBuilder 接口,该接口提供了一个 build() 接口方法,便于执行 Security 构建相关操作;

1
2
3
4
5
6
7
8
9
10
public interface SecurityBuilder<O> {

/**
- Builds the object and returns it or null.
*
- @return the Object to be built or null if the implementation allows it.
- @throws Exception if an error occurred when building the Object
*/
O build() throws Exception;
}

从 debug 的过程中可以看到,该 securityFilterChainBuilder 分别对应的是WebSecurityCofnigRestSecurityConfig中被重载 HttpSecurity 对象,可见,这里通过调用 securityFilterChainBuilder.build() 对其执行构建动作,该构建将会生成一个关键的 SecurityFilterChain 对象,而 FilterChainProxy 对象正式由多个 SecurityFilterChain 对象实例所构成,具体详情参考 HttpSecurity.build 中的子流程;

第二个地方,

然后另外一个重要的就是,通过一个 SecurityFilterChains 队列来构造FilterChainProxy 对象,并将其作为 Filter 接口对象返回;从前面的系列文章总我们可以知道,FilterChainProxy 实际上是 DelegatingFilterProxy 的一个代理……;

HttpSecurity.build

WebSecurity.build 第 3 点中可以知道,通过 HttpSecurity.build() 方法将会返回一个 SecurityFilterChain 对象,那么这个过程是怎样的呢?首先,HttpSecurity 同样实现了 SecurityBuilder 接口,并且其调用流程同样是通过 AbstractSecurityBuilder.build() 方法开始的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {

......

public final O build() throws Exception {
if (building.compareAndSet(false, true)) {
object = doBuild();
return object;
}
throw new AlreadyBuiltException("This object has already been built");
}

......

}

通过上述代码第 7 行,执行 doBuild() 逻辑,该方法将会调用,AbstractConfiguredSecurityBuilder.doBuild() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> extends AbstractSecurityBuilder<O> {

......

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

beforeInit();
init();

buildState = BuildState.CONFIGURING;

beforeConfigure();
configure();

buildState = BuildState.BUILDING;

O result = performBuild();

buildState = BuildState.BUILT;

return result;
}
}

......
}

可见该构建的流程同样是通过 init、configure 以及 perform build 三个流程所构成的;后续打算新开一个系列来专门讲解 HttpSecurity 对象逻辑,所以这里不打算对 HttpSecurity 的构建流程做深入的分析;直接跳转到最后一步,HttpSecurity.performBuild()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class HttpSecurity extends
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {

.......

@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparitor);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}

.......

}

可见这里将会初始化一个 DefaultSecurityFilterChain 并返回;

设计

从上述的分析后,笔者构建了相关的设计图来总结其相关的流程和逻辑;

类图

整个上述流程中所涉及的类如上图所示,让整个 Spring Security 流转起来的最核心的类是 WebSecurityConfiguration

Sequence

相关业务流程图如下,

看这张图的时候,还需要注意,WebSecurity 与 HttpSecurity 是一对多的关系;另外需要关注的是,从步骤 1.1.1.1.4.1.1.1.1 getHttp() 开始,初始化 HttpSecurity 实例,并为每一个 HttpSecurity 实例进行配置( 通过 SecurityConfigurer 进行配置 ),要注意的是 HttpSecurity 与用户自定义的 WebSecurityConfigRestSecurityConfig 是一对一的关系;当 HttpSecurity 初始化和初始配置结束以后,会将该实例通过 Step 1.1.1.1.4.1.1.1.2 webSecurity.addSecurityFilterChainBuilder(http) 步骤将其注入到 WebSecurity 实例中;

WebSecurity

与 WebSecurity 相互交织,有关联的概念主要有三个个方面;1、SecurityBuilder,既 WebSecurity 本身是一个 Security Builder 这样一个角色;2、WebSecurityConfigurer,这里主要是用来扩展用户自定义安全链的规则;3、WebSecurity 通过 apply() 方法加载用户自定义 WebSecurityConfigurers,也就是说 WebSecurity 包含一个或者多个 WebSecurityConfigurers 对象;下面笔者分别就这三个方面来进行描述;

SecurityBuilder

这部分内容是后续补记的内容,由类图可知,WebSecurity 本身是一个 SecurityBuilder,因此它支持三步构建的方式,这部分参考 WebSecurity.build

WebSecurityConfigurer

这部分描述了用户自定义的 WebConfigSecurityRestConfigSecurity 的类的形态;他们分别继承自 WebSecurityConfigurerAdapter 抽象类;

更多有关 SecurityConfigurer 的内容参考 Spring Security 源码分析九:Java config - HttpSecurity & SecurityConfigurer 小节 SecurityConfigurer 部分;

WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter 是非常核心的类,用来扩展用户自定义的 Security Chain 的规则,那么下面,笔者就相关的核心方法进行依次介绍

  1. configure(HttpSecurity http)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    protected void configure(HttpSecurity http) throws Exception {
    logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

    http
    .authorizeRequests()
    .anyRequest().authenticated()
    .and()
    .formLogin().and()
    .httpBasic();
    }

    该方法是标准的模板方法,目的是让用户来重载并提供自定义的安全链的相关设置;如果用户没有重载该方法,则使用上述的默认实现;

  2. configure(WebSecurity web)

    1
    2
    3
    4
    5
    6
    /**
    * Override this method to configure {@link WebSecurity}. For example, if you wish to
    * ignore certain requests.
    */
    public void configure(WebSecurity web) throws Exception {
    }

    默认提供的是一个空的模板方法,目的是让用户来重载该方法并定制 WebSecurity 的相关属性;该方法是在 sequence diagram 的 1.1.1.1.3.1 步骤中被调用;

  3. HttpSecurity getHttp()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    /**
    - Creates the {@link HttpSecurity} or returns the current instance
    *
    - ] * @return the {@link HttpSecurity}
    - @throws Exception
    */
    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);
    http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
    localConfigureAuthenticationBldr.getSharedObjects());
    http.setSharedObject(UserDetailsService.class, userDetailsService());
    http.setSharedObject(ApplicationContext.class, context);
    http.setSharedObject(ContentNegotiationStrategy.class, contentNegotiationStrategy);
    http.setSharedObject(AuthenticationTrustResolver.class, trustResolver);
    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<HttpSecurity>()).and()
    .logout();
    // @formatter:on
    }
    configure(http);
    return http;
    }

    在前面的 WebSecurity.build 小节的第 #1 步中,既是在 init build process 中,上述的 getHttp() 将会被调用,并且通过模板方法 configure(http) 去加载用户对 HttpSecurity 的相关的配置;

WebSecurityConfiguration

WebSecurityConfiguration 在 WebSecurityConfiguration.class 章节中有过细致的介绍;这里的讲解重心是 WebSecurity;

构造 WebSecurity 并加载 WebSecurityConfigurers

WebSecurityConfiguration.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {

webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));

Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);

Integer previousOrder = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used, so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
  • 代码第 7 行构造了 WebSecurity 对象;
  • 代码第 23 到 25 行,将用户自定义的 web configurers,WebSecurityConfig 和 RestSecurityConfig,载入 WebSecurity 对象实例中;

执行 WebSecurity 构建操作

WebSecurityConfiguration.class

1
2
3
4
5
6
7
8
9
10
11
12
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
return webSecurity.build();
}

代码第 11 行,开始执行 WebSecurity 实例的构建操作,该构建过程在 WebSecurity.build 小节中有过非常详细的介绍;

Sequence

参考 Sequence Diagram

总结(关联关系总结)

搞清楚以下的逻辑,也就搞清楚了 WebSecurity 的作用和地位了;

  • WebSecurity 与 FilterChainProxy 和 DelegatingFilterProxy 是一对一的关系;
    FilterChainProxy 对象是由 WebSecurity 对象所构建出来的;

  • WebSecurity 与 HttpSecurity 是一对多的关系;
    FilterChainProxy 对象包含多个安全链既 SecurityFilterChain 对象,HttpSecurity 将会负责构建出 SecurityFilterChain 对象;所以,WebSecuirty 包含多个 HttpSecurity;

  • HttpSecurity 与 SecurityFilterChain 是一对一的关系;