前言
本文是对 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/**,/** 这样三条相关的安全过滤链;
DelegatingFilterProxy
对应的也就是一个 Web Servlet Filters 中的一个 Filter;它是 Spring Boot MVC 启动以后六大 Filters 之一,并且排名第五;它将会把用户的访问请求( URL )转发到其 delegate 对象上既是 FilterChainProxy 来进行处理;DelegatingFilterProxy 是全局唯一的;
FilterChainProxy
FitlerChainProxy 被 DelegatingFilterProxy 代理,是 DelegatingFilterProxy 中的一个成员变量;FilterChainProxy 可以包含多个 Filter 构成一个 Spring Security 的安全链;FilterChainProxy 是全局唯一的;
SecurityFilterChain
包含多个与 Spring Security 安全相关的 Filters,或者可以说,SecurityFilterChain 就是由这些与 Spring Security 相关的 Filters 所构成的,比如 CsrfFilter、UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter 等;还有一个关键点,那就是每一个SecurityFilterChain
关联一个特定的 URL 格式,比如 /foo/** 或者是 /bar/**;SecurityFilterChain 在全局范围内可以包含多个;
搞懂了这三者之间的关系,也就基本上弄懂了 Spring Security 过滤链是如何构成的了;下面笔者将会带领大家就一些具体内容做深入的分析;
SecurityFilterChain
由上一小节什么是 Spring Security 过滤链的第 3 点我们知道,SecurityFilterChain
构成了一个特殊的安全认证链,该链条绑定一个特定的 URL 前缀;那么我们该如何创建一个 SecurityFilterChain 呢?下面我们以创建一个 /foo/** 安全链为例,看看我们该如何创建该 Spring Security 过滤链;
1 |
|
我们只需要继承WebSecurityConfigurerAdapter
并自定义一个相关的配置类 ApplicationConfigurerAdapter 进行相关配置即可;
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
在使用了 Spring Security 的默认情况下( 既是将 Spring Security 相关类库加入了当前的 classpath 但是并没有进行任何配置的情况下 ),在接近 Filter 链条的末尾的地方,会默认添加一个使用 Basic Auth 的方式进行验证的 Filter Chain,所以,在自定义自己的 FilterChain 的时候,需要把 Order 值设置得比它小,既是在它之前执行;查看 Order 排序相关内容了解通过 Order 是如何进行排序的;http.antMatcher
1
2
3
4
5
6
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
...
}表示开启一个访问链接前缀为 /foo/** 的过滤链
通过上述的方式所生成的 SecurityFilterChain 实例将会自动的注册到 FilterChainProxy 对象中,并处理 URL 链接匹配 /foo/** 的访问;
代码及流程分析
类图
DelegatingFilterProxy
由什么是 Spring Security 过滤链的概念分析中可知,DelegatingFilterProxy 将会把当前的请求转发给其代理 FilterChainProxy 来进行处理;
源码分析
笔者摘要部分相关核心源码如下,
1 | public class DelegatingFilterProxy extends GenericFilterBean { |
先来分析一下相关的成员变量
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
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
7protected 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
FilterChainProxy
是 DelegatingFilterProxy 类中的一个代理类,由类图可知,其包含了一个或者多个 SecurityFilterChain 对象;
笔者将相关部分核心源码摘录如下,
1 | public class FilterChainProxy extends GenericFilterBean { |
成员变量,
- filterChains
一个列表,由 SecurityFilterChain 对象所构成的列表;
方法,
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
7private 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
8private 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
3public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}可见,是通过 RequestMatcher 对象实例来进行匹配的;
SecurityFilterChain
从类图中可以清晰的看到,SecurityFilterChain
是一个接口,该接口由一个或者多个 Filters 所构成;Spring Security 提供了一个默认的实现,那就是DefaultSecurityFilterChain
;我们来看一下相关的源码,
1 | public final class DefaultSecurityFilterChain implements SecurityFilterChain { |
非常的简单,可见,它用一个 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,比如 CsrfFilter、UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter;
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,它们分别是,
- ApplicationFilterConfig[name=characterEncodingFilter, filterClass=org.springframework.boot.context.web.OrderedCharacterEncodingFilter]
- ApplicationFilterConfig[name=hiddenHttpMethodFilter, filterClass=org.springframework.boot.context.web.OrderedHiddenHttpMethodFilter]
- ApplicationFilterConfig[name=httpPutFormContentFilter, filterClass=org.springframework.boot.context.web.OrderedHttpPutFormContentFilter]
- ApplicationFilterConfig[name=requestContextFilter, filterClass=org.springframework.boot.context.web.OrderedRequestContextFilter]
- ApplicationFilterConfig[name=springSecurityFilterChain, filterClass=org.springframework.web.filter.DelegatingFilterProxy]
- 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: