Spring Security中各个Filter的理解

887 阅读6分钟

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

透过上面的求证,我们知道FilterChainProxyDelegatingFilterProxy中实际包含的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

FilterChainProxyprivate List<SecurityFilterChain> filterChains,说明存在多个SecurityFilterChain来执行拦截,而对于每一个SecurityFilterChain 拦截器链条来说,里面有macthes方法,如果匹配的话,才会去获取它的多个Filter去处理拦截。

所以这一层层的关系,还是比较绕的。这让笔者想起了Tomcat中的责任链模式,Servlet容器里面的Pipeline -Valve设计得很有趣,感兴趣的读者可以去看一看。

  • DelegatingFilterProxy
    • FilterChainProxy
      • List<SecurityFilterChain>
        • List<Filter>

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的添加和关联笔者没做详细说明,还望读者自己探求一下。

特别感谢本文的灵感来源小马哥以及阿里中间件的徐妈(也是巨人的肩膀中给出链接的作者)!

巨人的肩膀

  1. Spring Security(六)—SpringSecurityFilterChain 加载流程深度解析