Spring Security 源码分析八:Spring Security 过滤链一 - 概念与设计

前言

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

本文主要是对 Spring Security Filter Chain 进行描述和分析;

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

概念

这部分笔者带领大家首先来搞清楚相关的概念;

Web Servlet Filters ( Web Servlet 过滤器 )

要能够全面掌握什么是 Spring Security 过滤链,那么首先,我们必须得认识到什么是 Web Servlet Filters;通常一个 web 应用会通过多个 Servlet Filters 组成,如下图所示,

通常我们通过客户端去服务器上调用一个 web 资源的时候,首先,会通过一系列的过滤器 Filters 执行相关过滤操作,最终到达 Web Servlet ( 既是我们常说的 Controller );

Filter 包含两种类型,

  • 一个由用户自定义的实现了 Filter 接口的 Bean,一般而言,该 Bean 可以通过 @Order 注解或者实现 Ordered 接口来定义其加载的顺序;
  • Spring 内置的 Filter,比如 SessionRepositoryFilter;

Spring Security Filter Chain ( Spring Security 过滤链 )

什么是 Spring Security 过滤链

Spring Security 过滤链构成了 Spring Security 安全控制的核心;为了便于理解笔者画了下面这样一张图,总结了什么是 Spring Security 过滤链;

Spring Boot MVC 启动的时候,默认会加载 6 个 Filters,第5个便是与 Spring Security 过滤链相关的DelegatingFilterProxy;DelegatingFilterProxy 代理了一个对象既FilterChainProxy,一个 FilterChainProxy 可以包含 1 个或者多个SecurityFilterChain对象;每一个 SecurityFilterChain 又可以包含多个不同的 Filters 进而构成一个特定的安全链,如图,笔者分别构造了三条 Spring Security 过滤链( 既 SecurityFilterChain ),与之相关的分别是 /foo/**/bar/**/** 这样三条相关的安全过滤链;

  1. DelegatingFilterProxy
    对应的也就是一个 Web Servlet Filters 中的一个 Filter;它是 Spring Boot MVC 启动以后六大 Filters 之一,并且排名第五;它将会把用户的访问请求( URL )转发到其 delegate 对象上既是 FilterChainProxy 来进行处理;

    DelegatingFilterProxy 是全局唯一的;

  2. FilterChainProxy
    FitlerChainProxy 被 DelegatingFilterProxy 代理,是 DelegatingFilterProxy 中的一个成员变量;FilterChainProxy 可以包含多个 Filter 构成一个 Spring Security 的安全链;

    FilterChainProxy 是全局唯一的;

  3. SecurityFilterChain
    包含多个与 Spring Security 安全相关的 Filters,或者可以说,SecurityFilterChain 就是由这些与 Spring Security 相关的 Filters 所构成的,比如 CsrfFilterUsernamePasswordAuthenticationFilterBasicAuthenticationFilter 等;还有一个关键点,那就是每一个SecurityFilterChain关联一个特定的 URL 格式,比如 /foo/** 或者是 /bar/**

    SecurityFilterChain 在全局范围内可以包含多个;

搞懂了这三者之间的关系,也就基本上弄懂了 Spring Security 过滤链是如何构成的了;下面笔者将会带领大家就一些具体内容做深入的分析;

SecurityFilterChain

由上一小节什么是 Spring Security 过滤链的第 3 点我们知道,SecurityFilterChain构成了一个特殊的安全认证链,该链条绑定一个特定的 URL 前缀;那么我们该如何创建一个 SecurityFilterChain 呢?下面我们以创建一个 /foo/** 安全链为例,看看我们该如何创建该 Spring Security 过滤链;

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
.authorizeRequests()
.antMatchers("/foo/bar").hasRole("BAR")
.antMatchers("/foo/spam").hasRole("SPAM")
.anyRequest().isAuthenticated();
}
}

我们只需要继承WebSecurityConfigurerAdapter并自定义一个相关的配置类 ApplicationConfigurerAdapter 进行相关配置即可;

  1. @Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
    在使用了 Spring Security 的默认情况下( 既是将 Spring Security 相关类库加入了当前的 classpath 但是并没有进行任何配置的情况下 ),在接近 Filter 链条的末尾的地方,会默认添加一个使用 Basic Auth 的方式进行验证的 Filter Chain,所以,在自定义自己的 FilterChain 的时候,需要把 Order 值设置得比它小,既是在它之前执行;查看 Order 排序相关内容了解通过 Order 是如何进行排序的

  2. http.antMatcher

    1
    2
    3
    4
    5
    6
    @Override
    protected void configure(HttpSecurity http) throws Exception {

    http.antMatcher("/foo/**")
    ...
    }

    表示开启一个访问链接前缀为 /foo/** 的过滤链

通过上述的方式所生成的 SecurityFilterChain 实例将会自动的注册到 FilterChainProxy 对象中,并处理 URL 链接匹配 /foo/** 的访问;

代码及流程分析

类图

DelegatingFilterProxy

什么是 Spring Security 过滤链的概念分析中可知,DelegatingFilterProxy 将会把当前的请求转发给其代理 FilterChainProxy 来进行处理;

源码分析

笔者摘要部分相关核心源码如下,

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
public class DelegatingFilterProxy extends GenericFilterBean {

...

private WebApplicationContext webApplicationContext;

private String targetBeanName;

private volatile Filter delegate;

...

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
...

// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);

}

...

protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

delegate.doFilter(request, response, filterChain);
}

...

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}

}

先来分析一下相关的成员变量

  • WebApplicationContext webApplicationContext
    Spring ApplicationContext 对象;

  • String targetBeanName
    该值存放的是 “springSecurityFilterChain”,该值所对应的 Spring Bean 对象是 FilterChainProxy;插曲,因为种种原因,之前一直认为 “springSecurityFilterChain” 表示的是 DelegatingFilterProxy bean,后来经过严谨的源码分析,发现不是,查看“springSecurityFilterChain”对应的 Spring Bean 是什么?章节以了解更多详情;

  • volatile Filter delegate
    该对象是关键,代理的正是 FilterChainProxy 对象;该对象是在方法 initDelegate(WebApplicationContext wac) 中通过 bean name “springSecurityFilterChain” 进行初始化加载的;具体分析内容参考后续的方法分析部分内容;

再来分析一下相关的方法

  • doFilter()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
    // Lazily initialize the delegate if necessary.
    Filter delegateToUse = this.delegate;
    ...
    // Let the delegate perform the actual doFilter operation.
    invokeDelegate(delegateToUse, request, response, filterChain);
    }

    该方法非常的直观,直接将当前的请求转发给了其 delegate 进行处理;

  • invokeDelegate()
    调用 delegate 的 doFilter 方法;

  • initDelegate()

    1
    2
    3
    4
    5
    6
    7
    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
    if (isTargetFilterLifecycle()) {
    delegate.init(getFilterConfig());
    }
    return delegate;
    }

    通过 targetBeanName 来初始化并加载 delegate 对象,既是 FilterChainProxy 对象;这里的 targetBeanName 是 “springSecurityFilterChain”;所以,实际上 “springSecurityFilterChain” 代表的是 FilterChainProxy 对象;

“springSecurityFilterChain”所对应的 Spring Bean?

之前在分析源码的时候,数次将 bean name “springSecurityFilterChain” 所对应的 Spring Bean 认为是 DelegatingFilterProxy 对象;频频出现这样的认为,后来发现这是一个坑,所以在这里记录一下当时分析的时候遇到的坑,

在分析 DelegatingFilterProxy 初始化过程中的时候,当调试到其初始化的配置相关内容的时候,比如这里有关 DelegatingFilterProxy 所相关的 ApplicationFilterConfig 的时候,可以看到 [name = springSecurityFilterChain, filterClass=org.springframework.web.filter.DelegatingFilterProxy] 所以也就频频让我误认为 “springSecurityFilterChain” 所对应的正是 DelegatingFilterProxy Spring Bean 对象;

其实,上述的 [name = springSecurityFilterChain, filterClass=org.springframework.web.filter.DelegatingFilterProxy] 的正确的理解是 “springSecurityFilterChain” 是由 DelegatingFilterProxy 所代理的 delegate bean 的 bean name,所对应的正是 FilterChainProxy 对象;这一点可由上一小节有关 initDelegate() 方法的论述来加以论证;

FilterChainProxy

FilterChainProxyDelegatingFilterProxy 类中的一个代理类,由类图可知,其包含了一个或者多个 SecurityFilterChain 对象;

笔者将相关部分核心源码摘录如下,

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
public class FilterChainProxy extends GenericFilterBean {

...

private List<SecurityFilterChain> filterChains;

...

public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
}

...
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
...
doFilterInternal(request, response, chain);
...

}

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

...

List<Filter> filters = getFilters(fwRequest);

...

VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}

...

private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}

return null;
}

}

成员变量,

方法,

  • public FilterChainProxy(List<SecurityFilterChain> filterChains)
    构造函数,可见,FilterChainProxy 是通过一系列的 SecurityFilterChain 所构造而成的;

  • public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain )
    首先要特别注意的是,这里的参数 FilterChain chain;该参数所代表的是 java.servlet.FilterChain,是 Servlet 对象所使用到的访问链;该方法的核心逻辑既是将当前的请求传递给方法 doFilterInternal 进行处理,下面我们就来看看 doFilterInternal 的相关逻辑

  • private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)

    1
    2
    3
    4
    5
    6
    7
    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    ...
    List<Filter> filters = getFilters(fwRequest);
    ...
    VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
    vfc.doFilter(fwRequest, fwResponse);
    }

    这段代码的核心就两个,

    一、通过 getFilters 得到相关的 filters;这里返回的是某个被匹配的 SecurityFilterChain 的所有相关的 filters,具体详情参考 getFilters 方法;

    二、对上述所得到的 filters 进行操作;

  • List<Filter> getFilters(HttpServletRequest request)

    1
    2
    3
    4
    5
    6
    7
    8
    private List<Filter> getFilters(HttpServletRequest request) {
    for (SecurityFilterChain chain : filterChains) {
    if (chain.matches(request)) {
    return chain.getFilters();
    }
    }
    return null;
    }

    可见,会遍历当前所有相关的 SecurityFilterChain 实例,如果有任何一个实例与当前的 request 相匹配,则返回其所有相关的 filters;那么,getFilters 方法又是如何通过 request 与当前的 chain 进行匹配的呢?我们来看一下 DefaultSecurityFilterChain#matches 方法是如何实现的

    1
    2
    3
    public boolean matches(HttpServletRequest request) {
    return requestMatcher.matches(request);
    }

    可见,是通过 RequestMatcher 对象实例来进行匹配的;

SecurityFilterChain

类图中可以清晰的看到,SecurityFilterChain是一个接口,该接口由一个或者多个 Filters 所构成;Spring Security 提供了一个默认的实现,那就是DefaultSecurityFilterChain;我们来看一下相关的源码,

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
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;

public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}

public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
this.requestMatcher = requestMatcher;
this.filters = new ArrayList<Filter>(filters);
}

public RequestMatcher getRequestMatcher() {
return requestMatcher;
}

public List<Filter> getFilters() {
return filters;
}

public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}

@Override
public String toString() {
return "[ " + requestMatcher + ", " + filters + "]";
}
}

非常的简单,可见,它用一个 List 对象包含了多个 Filter filters 对象;并通过 requestMatcher 对象来与当前的 request 进行匹配,判断是否与该 SecurityFilterChain 对象相匹配;

Sequence diagram

如图,当客户端发起一个 /foo/** 的请求,首先会经过 Servlet Filter 逻辑将请求转发至DelegatingFilterProxy对象上,DelegatingFilterProxy 然后将此请求转发给FilterChainProxy上,FilterChainProxy 依次迭代所有的SecurityFilterChain,并调用其 matches(request) 方法找到与当前请求所匹配的 SecurityFilterChain 对象,然后返回其所有的 Filters,最后,依次执行这些Filters,这些 Filters 就是与 Spring Security 密切相关的 Filters,比如 CsrfFilterUsernamePasswordAuthenticationFilterBasicAuthenticationFilter

Ordered 是如何实现的?

SecurityFilterChain 的例子中,可以看到该例子实现了 @Order 注解,那么问题是,@Order 的顺序是如何实现的呢?

断点打在 DelegatingFilterProxy#initDelegate(wac) 上,查看 WebSecurityConfiguration#setFilterChainProxySecurityConfigurer() 实现,

代码第 142 行,就是根据 @Order 对 SecurityFilterChain 进行排序操作;

6 个 Web Servlet Filters

正如什么是 Spring Security 过滤链小节所描述的那样,DelegatingFilterProxy 是 6 大 Servlet Filters 之一,那么这 6 个 Filters 分别是什么?下面笔者将带领大家看看,如何获取该 6 大 Servlet Filters 的信息,

首先,将断点打在 FilterChainProxy.doFilter() 方法中;然后,模拟用户登录过程,将得到如下的 debug tree,

查看 ApplicationFilterChain.internalDoFilter(request, response) 方法,

可见,在进行 DelegatingFilterProxy 之前,总共经历了 6 大核心的 Filters,它们分别是,

  1. ApplicationFilterConfig[name=characterEncodingFilter, filterClass=org.springframework.boot.context.web.OrderedCharacterEncodingFilter]
  2. ApplicationFilterConfig[name=hiddenHttpMethodFilter, filterClass=org.springframework.boot.context.web.OrderedHiddenHttpMethodFilter]
  3. ApplicationFilterConfig[name=httpPutFormContentFilter, filterClass=org.springframework.boot.context.web.OrderedHttpPutFormContentFilter]
  4. ApplicationFilterConfig[name=requestContextFilter, filterClass=org.springframework.boot.context.web.OrderedRequestContextFilter]
  5. ApplicationFilterConfig[name=springSecurityFilterChain, filterClass=org.springframework.web.filter.DelegatingFilterProxy]
  6. ApplicationFilterConfig[name=Tomcat WebSocket (JSR356) Filter, filterClass=org.apache.tomcat.websocket.server.WsFilter]

可以看到与 Spring Security 相关 DelegatingFilterProxy 所在的位置是在第 5 个位置;最后一个是与 WebSocket 相关的 Filter;

Reference

https://spring.io/guides/topicals/spring-security-architecture/

Spring Security Java Config Preview 1:
Spring Security Java Config Preview 2:https://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/
Spring Security Java Config Preview 3: