DelegatingFilterProxy
注册方式
方式一:Servlet 3.0+ 的 Java Config
DelegatingFilterProxy是Java EE范畴中的javax.servlet.Filter。
WebApplicationInitializer
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
WebApplicationInitializer可以看做是Web.xml的替代,它是一个接口。通过实现WebApplicationInitializer,在其中可以添加servlet,listener等,在加载Web项目的时候会加载这个接口实现类,从而起到web.xml相同的作用。
这是一个比较常见的场景,你可能还没有使用 SpringBoot 内嵌的容器,将项目打成 war 包部署在外置的应用容器中,比如最常见的 tomcat,一般很少 web 项目低于 servlet3.0 版本的,并且该场景摒弃了 XML 配置。
注册原理:主要自定义一个 SecurityWebApplicationInitializer 并且让其继承自 AbstractSecurityWebApplicationInitializer 即可。
AbstractSecurityWebApplicationInitializer
@Override
public final void onStartup(ServletContext servletContext) {
// 模板方法
beforeSpringSecurityFilterChain(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
if (enableHttpSessionEventPublisher()) {
servletContext.addListener("org.springframework.security.web.session.HttpSessionEventPublisher");
}
servletContext.setSessionTrackingModes(getSessionTrackingModes());
insertSpringSecurityFilterChain(servletContext);
// 模板方法
afterSpringSecurityFilterChain(servletContext);
}
insertSpringSecurityFilterChain
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
/**
* Registers the springSecurityFilterChain
* @param servletContext the {@link ServletContext}
*/
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME; // springSecurityFilterChain
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
this.setTargetBeanName(targetBeanName);
this.webApplicationContext = wac;
if (wac != null) {
this.setEnvironment(wac.getEnvironment());
}
}
registerFilter
filterName :
springSecurityFilterChain
private void registerFilter(ServletContext servletContext, boolean insertBeforeOtherFilters, String filterName,
Filter filter) {
// filterName: springSecurityFilterChain
// filter: DelegatingFilterProxy
Dynamic registration = servletContext.addFilter(filterName, filter);
Assert.state(registration != null, () -> "Duplicate Filter registration for '" + filterName
+ "'. Check to ensure the Filter is only configured once.");
registration.setAsyncSupported(isAsyncSecuritySupported());
EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters, "/*");
}
方式二 : web.xml
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
方式三:SpringBoot 内嵌应用容器并且使用自动配置
内嵌容器是完全不会使用 SPI 机制加载 servlet3.0 新特性的那些 Initializer 的 ,spring boot场景下,方式一无效
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,
SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;//springSecurityFilterChain
// <1>
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
// DelegatingFilterProxyRegistrationBean 作用便是在 Spring Boot 环境下通过 Tomcat Starter 等内嵌容器启动类来注册一个 DelegatingFilterProxy
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilterOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
@Bean
@ConditionalOnMissingBean
public SecurityProperties securityProperties() {
return new SecurityProperties();
}
}
生命周期
DelegatingFilterProxy类位于spring-web模块下,继承了GenericFilterBean。
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {...}
DelegatingFilterProxy首先是Java EE的Filter,其次还可以作为Spring中的Bean
我们重点讲述它作为Filter的生命周期
初始化
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
// 获取web.xml中配置的<init-param>
this.filterConfig = filterConfig;
// Set bean properties from init parameters.重点看这句话
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
Environment env = this.environment;
if (env == null) {
env = new StandardServletEnvironment();
}
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
}
}
// Let subclasses do whatever initialization they like.
initFilterBean();
}
initFilterBean()
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
-
targetBeanName假如为空,则将其设置为
DelegatingFilterProxy自己的Filter Name -
获取Spring应用上下文,如果上下文信息存在,则去获取委派的
Filter@Nullable private volatile Filter delegate; protected Filter initDelegate(WebApplicationContext wac) throws ServletException { // 获取委派的Filter 名称 String targetBeanName = getTargetBeanName(); Assert.state(targetBeanName != null, "No target bean name set"); // 根据名称和类型依赖查找 实际委托的Filter Bean Filter delegate = wac.getBean(targetBeanName, Filter.class); if (isTargetFilterLifecycle()) { delegate.init(getFilterConfig()); } return delegate; }这样初始化完成后,我们就可以获取实际委派的
Filter,而它其实就是FilterChainProxy。
doFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
-
假设通过initFilterBean没有创建出Filter,则此时再去重新创建一次
Filter -
invokeDelegate
protected void invokeDelegate( Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { delegate.doFilter(request, response, filterChain); }Filter delegate -> @Nullable private volatile Filter delegate;
Filter寻找
所以说我们这个delegate到底是谁呢,根据前文我们知道是通过targetBeanName(默认为springSecurityFilterChain)和Filter.class在Spring上下文中查找到的Filter Bean 实例。
public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {
private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
我们在IDEA中,把鼠标放到DEFAULT_FILTER_NAME,按下ALT+F7,查看当前的常量被哪些地方使用了。
springSecurityFilterChain()
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration这个类中就定义了一个满足条件的Bean
/**
* Creates the Spring Security Filter Chain
* @return the {@link Filter} that represents the security filter chain
* @throws Exception
*/
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
Assert.state(!(hasConfigurers && hasFilterChain),
"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
if (!hasConfigurers && !hasFilterChain) {
WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
this.webSecurity.apply(adapter);
}
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
for (Filter filter : securityFilterChain.getFilters()) {
if (filter instanceof FilterSecurityInterceptor) {
this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
break;
}
}
}
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
return this.webSecurity.build();
}
最直截了当的代码是
this.webSecurity.build(),
private WebSecurity webSecurity;这是
WebSecurityConfiguration类中的实例变量。
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
@Override
public final O build() throws Exception {
// 线程安全考虑
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
...
}
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild
@Override
protected final O doBuild() throws Exception {
synchronized (this.configurers) {
this.buildState = BuildState.INITIALIZING;
beforeInit();
init();
this.buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
this.buildState = BuildState.BUILDING;
O result = performBuild();
this.buildState = BuildState.BUILT;
return result;
}
}
WebSecurity#performBuild
FilterChainProxy原来才是实际的Filter
@Override
protected Filter performBuild() throws Exception {
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
// 我们找到了实际的Filter
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (this.httpFirewall != null) {
filterChainProxy.setFirewall(this.httpFirewall);
}
if (this.requestRejectedHandler != null) {
filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
this.postBuildAction.run();
return result;
}
@EnableWebSecurity
@EnableWebMvc/EnableTransactionManagement同属于EnableXXX模式,通常用于激活某一模块。
@EnableWebSecurity则是用于激活Spring Security的自动装配
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
会导入WebSecurityConfiguration为Spring中的Bean Class,然后通过@Configuration /@Bean的模式来把FilterChainProxy(Filter Bean)注册到Spring IoC容器中。
@Bean(......)
public Filter springSecurityFilterChain() throws Exception {
}
FilterChainProxy
透过上面的求证,我们知道FilterChainProxy是DelegatingFilterProxy中实际包含的Filter Bean。
public class FilterChainProxy extends GenericFilterBean {
private List<SecurityFilterChain> filterChains;
...
}
FilterChainProxy继承了GenericFilterBean,说明其具有Spring中的一些回调接口来做一些额外的处理,尤其是xxxAware接口。
推断 FilterChainProxy 的名字就可以发现,它依旧不是真正实施过滤的类,它内部维护了一个 SecurityFilterChain,这个过滤器链才是请求真正对应的过滤器链,并且同一个 Spring 环境下,可能同时存在多个安全过滤器链,如 private List<SecurityFilterChain> filterChains所示,需要经过 chain.matches(request) 判断到底哪个过滤器链匹配成功,每个 request 最多只会经过一个 SecurityFilterChain
SecurityFilterChain
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
实际上到了这里,我们可以稍微总结一下,我们在Spring IoC容器外部配置了DelegatingFilterProxy,这个Filter执行拦截或者初始化时会去Spring上下文中根据名称和类型依赖查找获取Filter Bean,后续的拦截逻辑,由Filter Bean来完成,而Filter Bean我们根据对源码的定位,不难得知,其实就是FilterChainProxy;
而FilterChainProxy中private List<SecurityFilterChain> filterChains,说明存在多个SecurityFilterChain来执行拦截,而对于每一个SecurityFilterChain 拦截器链条来说,里面有macthes方法,如果匹配的话,才会去获取它的多个Filter去处理拦截。
所以这一层层的关系,还是比较绕的。这让笔者想起了Tomcat中的责任链模式,Servlet容器里面的Pipeline -Valve设计得很有趣,感兴趣的读者可以去看一看。
- DelegatingFilterProxy
- FilterChainProxy
List<SecurityFilterChain>List<Filter>
- FilterChainProxy
DefaultSecurityFilterChain
Standard implementation of {@code SecurityFilterChain}.
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public List<Filter> getFilters() {
return filters;
}
public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}
}
这是SecurityFilterChain唯一的标准实现。
总结
本文对于三个核心的Filter做了介绍,使得读者可以理清Spring Security中的过滤器链条的初始化和调用逻辑;而关于SecurityFilterChain和其内部关联的Filter的添加和关联笔者没做详细说明,还望读者自己探求一下。
特别感谢本文的灵感来源小马哥以及阿里中间件的徐妈(也是巨人的肩膀中给出链接的作者)!