Spring Security 源码分析九:Java config - HttpSecurity & SecurityConfigurer

前言

本文是对 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
2
3
4
5
6
7
8
9
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}

等价于做了如下的配置

1
2
3
4
5
<http>
<intercept-url pattern="/**" access="authenticated"/>
<form-login />
<http-basic />
</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
2
3
4
5
6
7
8
9
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 -> 6
// 允许访问
.permitAll();

再次,通过如下的方式配置了另一条 /rest/** 的安全过滤链,

1
2
3
4
5
6
7
http.antMatcher("/rest/**") // the filter chain defined for web request;
.csrf().disable() // rest 请求无需 CSRF Token;
.authorizeRequests()
.antMatchers("/rest/hello").hasRole("USER")
.anyRequest().authenticated()
.and()
.httpBasic();

上述的配置等价如下的配置,

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
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/web/**" filters="
securityContextPersistenceFilter,
logoutFilter,
usernamePasswordAuthenticationFilter,
exceptionTranslationFilter,
filterSecurityInterceptor">
<sec:intercept-url pattern="/web/report/**" access="ROLE_MANAGER"/>
</sec:filter-chain>
<sec:filter-chain pattern="/rest/**" filters="
securityContextPersistenceFilter,
logoutFilter,
basicAuthenticationFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" >
<sec:intercept-url pattern="/rest/report/**" access="ROLE_USER"/>
</sec:filter-chain>
</sec:filter-chain-map>
</bean>

<!-- logout filter -->
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<!-- the post-logout destination -->
<constructor-arg value="/"/>
<constructor-arg>
<array>
<ref local="logoutHandler"/>
<!--高级配置 添加 rememberMe-->
<ref local="rememberMeServices"/>
</array>
</constructor-arg>
<property name="filterProcessesUrl" value="/logout"/>
</bean>

<bean id="basicAuthenticationFilter" ....>

......

</bean>

......

相关配置说明和其它配置元素

还是以上述单个 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
2
3
<sec:filter-chain pattern="/web/**" ... > 
....
</sec:filter-chain>

开启了一个新的 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
2
3
<session-management>
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>

该方法返回的是 SessionManagementConfigurer 对象,该部分将会在后面的类图部分有更详细的介绍;

authenticationProvider( authenticationProvider ) : HttpSecurity

Java Config 中的配置元素 authenticationProvider( authenticationProvider ) 类似于进行如下的 XML 配置,

1
2
3
<authentication-manager>
<authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

该方法返回的是当前的 HttpSecurity 对象;

userDetailsService( userDetailsService ) : HttpSecurity

Java Config 中的配置元素 userDetailsService() 类似于进行如下的 XML 的配置,

1
2
3
<beans:bean id="myUserDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>

portMapper() : PortMapperConfigurer

Java Config 中的配置元素 portMapper() 类似于进行如下的 XML 配置,

1
2
3
<port-mappings>
<port-mapping http="9080" https="9443"/>
</port-mappings>

exceptionHandling() : ExceptionHandlingConfigurer

Java Config 中的配置元素 exceptionHandling() 类似于进行如下的 XML 的配置,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="exceptionTranslationFilter"
class="org.springframework.security.web .access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</bean>

<bean id="authenticationEntryPoint"
class="org.springframework.security.web .authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.do"/>
</bean>
<bean id="accessDeniedHandler"
class="org.springframework.security.web .access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.do"/>
</bean>

从 ExceptionHandlingConfigurer 的配置方法 configure() 中也可以看到这个逻辑,

1
2
3
4
5
6
7
8
9
10
11
@Override
public void configure(H http) throws Exception {
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
entryPoint, getRequestCache(http));
if (accessDeniedHandler != null) {
exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler);
}
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
http.addFilter(exceptionTranslationFilter);
}

其它配置

SpringSecurity class method

如图,展示了 HttpSecuirty 所有相关的配置项,里面还包含 csrf()logout()anonymous()requiresChannel() 等等非常重要的一系列的配置方法;

References

有关 XML 的配置参考 https://docs.spring.io/spring-security/site/docs/3.0.x/reference/ns-config.html

HttpSecurity

Spring Security SecurityBuilder 类图

SecurityBuilder 的实体类总共有三个,HttpSecurity、WebSecurity 以及 AuthenticationManagerBuilder;下面笔者将逐个的分析每一个相关的类;

SecurityBuilder

SecurityBuilder 接口非常的简单,提供了一个 build() 方法,顾名思义,主要是用来执行构建操作的;

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;
}

构建完成以后,将会返回对象 O;

AbstractSecurityBuilder

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
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();

private O object;

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

/**
* @return the Object that was built
*/
public final O getObject() {
if (!building.get()) {
throw new IllegalStateException("This object has not been built");
}
return object;
}

protected abstract O doBuild() throws Exception;
}

三个方法,

  • build()
    执行构建动作,通过调用抽象方法 build() 完成构建;

  • getObject()
    获得构建成功以后的对象;

  • doBuild()
    抽象方法,供子类实现其构建方法;

AbstractConfiguredSecurityBuilder

该抽象类中的逻辑非常的关键了,将其核心关键代码摘要如下,

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> 
extends AbstractSecurityBuilder<O> {

......

@SuppressWarnings("unchecked")
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
throws Exception {
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
add(configurer);
return configurer;
}

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

......

/**
* Sets an object that is shared by multiple {@link SecurityConfigurer}.
*
* @param sharedType the Class to key the shared object by.
* @param object the Object to store
*/
@SuppressWarnings("unchecked")
public <C> void setSharedObject(Class<C> sharedType, C object) {
this.sharedObjects.put((Class<Object>) sharedType, object);
}

@SuppressWarnings("unchecked")
public <C> C getSharedObject(Class<C> sharedType) {
return (C) this.sharedObjects.get(sharedType);
}

......

@SuppressWarnings("unchecked")
private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {

Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer.getClass();

synchronized (configurers) {

....

List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers.get(clazz) : null;

....

configs.add(configurer);
this.configurers.put(clazz, configs);

....
}

}

......

@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;
}
}

......

/**
* Subclasses must implement this method to build the object that is being returned.
*
* @return
*/
protected abstract O performBuild() throws Exception;

@SuppressWarnings("unchecked")
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}

for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}

@SuppressWarnings("unchecked")
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}

}

好些个核心的方法,下面我们一个一个的来屡吧,

  • apply(C configurer)
    这里笔者罗列了两个 apply(C configurer) 方法,目的是为了描述一个调试的 bug,HttpSecurity 的 getOrApply(configurer) 方法,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SuppressWarnings("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
    4
    public <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
    @SuppressWarnings("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
      3
      buildState = 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
      @SuppressWarnings("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
      @Override
      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
2
3
4
5
6
7
8
9
@SuppressWarnings("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);
}

更多有关此部分的内容参考 AbstractConfiguredSecurityBuilderapply(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

Spring Security Configurer 类图

由类图可知,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 上;
Spring Security SecurityBuilder 类图

HttpSecurity.build 的介绍可知 HttpSecurity 的构建的目的就是生成 Filters,然后将这些 Filters 作为构造参数初始化 DefaultSecurityFilterChain 对象并返回;而过程中,Filters 的生成就与 configurers 的方法 configure() 息息相关了;下面,笔者就相关的方法介绍如下,

HttpBasicConfigurer.configure(B http)

参数 B 指的是泛型 Builder,这里既是 HttpSecurity;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void configure(B http) throws Exception {
AuthenticationManager authenticationManager = http
.getSharedObject(AuthenticationManager.class);
BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(
authenticationManager, authenticationEntryPoint);
if (authenticationDetailsSource != null) {
basicAuthenticationFilter
.setAuthenticationDetailsSource(authenticationDetailsSource);
}
RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
if(rememberMeServices != null) {
basicAuthenticationFilter.setRememberMeServices(rememberMeServices);
}
basicAuthenticationFilter = postProcess(basicAuthenticationFilter);
http.addFilter(basicAuthenticationFilter);
}

下面笔者就 HttpBasicConfigurer 的配置的过程做进行详细的分析,

  • 代码 3 ~ 4 行,从SharedObject中获取得到 AuthenticationManager 对象;
  • 代码 5 ~ 6 行,生成 BasicAuthenticationFilter 对象,Filter
  • 代码 11 ~ 14 行,从 SharedObject 中获取得到 RememberMeServices 对象,并注入到 BasicAuthenticationFilter 对象中;
  • 代码 16 行,将 BasicAuthenticationFilter 加入到 HttpSecurity 实例中;

ExpressionUrlAuthorizationConfigurer

该对象的 configure 方法在其父类 AbstractInterceptUrlConfigurer 中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void configure(H http) throws Exception {
FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
if (metadataSource == null) {
return;
}
FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(
http, metadataSource, http.getSharedObject(AuthenticationManager.class));
if (filterSecurityInterceptorOncePerRequest != null) {
securityInterceptor
.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
}
securityInterceptor = postProcess(securityInterceptor);
http.addFilter(securityInterceptor);
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}

可见,目的正是为了生成一个 FilterSecurityInterceptor 既 Filter 对象,并将其载入到 HttpSecurity 对象中;

FormLoginConfigurer

同样,该对象的 configure 方法也是在其父类 AbstractAuthenticationFilterConfigurer 中;

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
@Override
public void configure(B http) throws Exception {
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if (portMapper != null) {
authenticationEntryPoint.setPortMapper(portMapper);
}

authFilter.setAuthenticationManager(http
.getSharedObject(AuthenticationManager.class));
authFilter.setAuthenticationSuccessHandler(successHandler);
authFilter.setAuthenticationFailureHandler(failureHandler);
if (authenticationDetailsSource != null) {
authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
if (sessionAuthenticationStrategy != null) {
authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
RememberMeServices rememberMeServices = http
.getSharedObject(RememberMeServices.class);
if (rememberMeServices != null) {
authFilter.setRememberMeServices(rememberMeServices);
}
F filter = postProcess(authFilter);
http.addFilter(filter);
}

上面代码的核心在 authFilter,而该 authFilter 正是 UsernamePasswordAuthenticationFilter,该 authFilter 的初始化过程在 FormLoginConfigurer 的构造函数中,

1
2
3
4
5
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}

UserDetailsAwareConfigurer

UserDetailsAwareConfigurer 主要涉及两个实体类 InMemoryUserDetailsManagerConfigurerJdbcUserDetailsManagerConfigurer;顾名思义,这两个类都是对用户身份信息进行验证的类;如类图中所示,这两个 configurers 将会作为 AuthenticationManagerBuilder 的 configures 来进行构建,最终构建的结果是返回一个 ProviderManager 对象;有关这部分内容笔者将会在下一章节对其进行描述;

总结

正如对 WebSecurity 的总结那样,WebSecurity 与 HttpSecurity 的关系是一对多的关系,也就是说,HttpSecurity 正是为 Spring Security 构建多个安全链 SecurityFilterChain 的核心类;WebSecurity 将构建出 FilterChainProxy, FilterChainProxy 包含多个 SecurityFilterChain 既安全链;