Spring Security 源码分析九:Java config - 加载 DelegatingFilterProxy

前言

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

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

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

简介

由前面的文章分析可知 DelegatingFilterProxyWeb Servlet Filters 中的一个 Filter;该 Filter 代理了 FilterChainProxy;FilterChainProxy 又包含了多个 SecurityFilterChain 对象;由此,整个 Spring Security 的过滤链的机制由此就建立起来了;但是问题来了,DelegatingFilterProxy 本身作为 Filter 是如何注册到 Spring Web 容器中的呢?本文,笔者将会带领读者们来回答这个问题;

笔者使用的环境是 Spring Boot,该加载的方式与 Spring MVC 的方式有很大的异同;Spring MVC 的加载方式可以参考 http://www.tianshouzhi.com/api/tutorials/spring_security_4/262 章节“过滤器注册过程的源码分析”部分,该部分详细的介绍了如何通过 ServletContainerInitializer 来初始化并加载过滤器 Filters;

DelegatingFilterProxy 载入流程分析

DelegatingFilterProxyRegistrationBean

本小节将记录笔者为了搞清楚在 Spring Boot 环境下 DelegatingFilterProxy 是如何作为 Filter 加载入 Spring Web 容器中的;

首先,将断点打在 DelegatingFilterProxy(String targetBeanName, WebApplicationContext wac) 中,我们将会得到如下的调试状态,

如图所示,Spring Boot 使用的是 TomcatEmbeddedContext 的方式来加载的 Filters,所以和 Spring MVC 通过使用 AbstractSecurityWebApplicationInitializer 来加载 Filter 的方式不同,下面我们就这种方式来进行分析,看看是如何通过 TomcatEmbeddedContext 一步一步的来加载 Filters 的?

然后,看 AnnotationConfigEmbeddedWebApplicationContext.setInitialize(ServletContext) 方法

答案就在 getServletContextInitializerBeans() 方法中,该方法返回如下对象列表;

是的,我发现了什么,似曾相似,这不就是前文提到的 6 大 Filters;而DelegatingFilterProxyRegistrationBean所代表的正是DelegatingFilterProxy;下面深入 getServletContextInitializerBeans() 方法,

上面的代码非常的关键了,通过传入 BeanFactory 作为构造参数来初始化构造 ServletContextInitializerBeans 的来获得这 6 大 Filters; 这里需要注意的一个细节是,ServletContextInitializerBeans 本身是一个 Collection 类型;

  1. addServletContextInitializerBeans(ListableBeanFactory beanFactory)
    可见构造函数中首先通过 addServletContextInitializerBeans(ListableBeanFactory beanFactory),然后通过在 Spring 容器中通过获取类型为 ServletContextInitializer 的 Spring Beans,DelegatingFilterProxyRegistrationBean和其余 5 大 Filters 均实现了该接口;所以,DelegatingFilterProxyRegistrationBean 以及其余 Filters 将会在这里被取出;

  2. addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory)
    该方法将对应的 Filter 加入 initializers 中,既是将 Filter 注入到 Spring Web 容器中;

从上述的第 #1 点可以知道,通过从容器中查找类型为 ServletContextInitializer 的 bean,既可以找到 DelegatingFilterProxyRegistrationBean 和其余 5 大 Filters;那么问题是,这些 Filters 是如何提前被注入到 Spring 容器中的呢?

RegistrationBean

本小节将回答上一小节末尾所提到的问题,那就是DelegatingFilterProxyRegistrationBean是如何作为 Filter 注入到 Spring 容器中的?其实,它的答案正是 org.springframework.boot.context.embedded.RegistrationBean 所涉及到的逻辑,该部分逻辑也正是 Spring Boot 的专属逻辑;下面,笔者将简要介绍一下DelegatingFilterProxyRegistrationBean做为 Filter 被注入到 Spring 容器中的逻辑,看一下 org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration 类的逻辑

通过 SecurityFilterAutoConfiguration 自动的将DelegatingFilterProxyRegistrationBean注入到 Spring Web 容器中;Spring Boot 放弃了以往类似 Spring MVC 加载 Filter 繁琐的方式,直接通过 RegisterBean 的方式来注册 ServletContext 中所需要的资源;这里不打算细说有关 RegistrationBean 的相关的逻辑,将来打算将这部分内容在 Spring Boot 的相关章节中做进一步描述;

Reference: http://www.tianshouzhi.com/api/tutorials/springboot/89

FilterChainProxy 作为代理载入 DelegatingFilterProxy 的流程

这里不打算做深入的分析了,下面描述了该载入流程的入口方法,

DelegatingFilterProxy.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Initialize the Filter delegate, defined as bean the given Spring
* application context.
* <p>The default implementation fetches the bean from the application context
* and calls the standard {@code Filter.init} method on it, passing
* in the FilterConfig of this Filter proxy.
* @param wac the root application context
* @return the initialized delegate Filter
* @throws ServletException if thrown by the Filter
* @see #getTargetBeanName()
* @see #isTargetFilterLifecycle()
* @see #getFilterConfig()
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}

核心逻辑在上述代码的第 16 行;target name 既是 “springSecurityFilterChain”,而该 bean name 对应的正好是 FilterChainProxy Spring Bean;