3.2 创建与注册过滤器流程的源码分析
3.1节我们介绍了在一个web应用中,Spring Security的配置实际上也包含了创建过滤器与注册过滤器两个过程。因此本节的源码分析也分为2个部分讲解,第一部分是创建过滤器过程的源码分析,第二部分是注册过程的源码分析。
过滤器创建流程的源码分析
当我们在任意一个类上添加了一个注解@EnableWebSecurity
,就可以创建一个名为 springSecurityFilterChain 的Filter。在上一节中,我们是在一个自定义的SecurityConfig类上加了这个注解。SecurityConfig类同时也继承了WebSecurityConfigurerAdapter
类,不过需要注意的是,这个过滤器的创建是通过@EnableWebSecurity完成的,与是否继承这个类无关。关于WebSecurityConfigurerAdapter这个类我们在后面会详细讲解,目前所需知道的是,即使我们自定义的类,不继承这个类,也可以自动帮我们创建一个Filter。
我们带着2个问题来考虑这个名字为springSecurityFilterChain 的Filter的创建过程:
1、这个Filter是在哪里创建的?
2、这个Filter的实现类是什么?
1、springSecurityFilterChain 过滤器的创建的位置
为什么在一个类上添加一个@EnableWebSecurity注解后,就可以自动创建一个Filter呢?我们首先看一下@EnableWebSecurity的源码:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class, ObjectPostProcessorConfiguration.class, SpringWebMvcImportSelector.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { /** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false; }
可以看到,这个注解上面加了@Configuration
、@EnableGlobalAuthentication
、@Import
三个注解,因此,当我们把这个注解加在某个类之上,就相当于在这个类上面同时加上了这三个注解。在第三章中,我们将这个注解加在SecurityConfig类上,意味着相当于在SecurityConfig类上加上了这个三个注解。
其中@Import这个注解中导入了三个配置类:WebSecurityConfiguration
、ObjectPostProcessorConfiguration
、SpringWebMvcImportSelector
,我们的Filter实际上就是由WebSecurityConfiguration
这个类创建,查看其核心部分源码:
@Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { private WebSecurity webSecurity; .... private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers; .... //创建一个名为springSecurityFilterChain的Filter @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty(); if (!hasConfigurers) { WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor .postProcess(new WebSecurityConfigurerAdapter() { }); webSecurity.apply(adapter); } return webSecurity.build(); } .... }
可以看到这个配置类中生成了一个名字为AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME的Filter(读者目前不要过多关注实现细节,后面会详细讲解),这是一个静态常量,其定义如下:
public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer { .... public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain"; ... }
因此,这段配置的作用实际上是,创建一个Spring Bean,Bean的类型是Filter,名字为springSecurityFilterChain
。只要我们保证自定义的SecuirtyConfig类,可以被Spring扫描到,就可以帮助我们创建这个Filter了。
2、springSecurityFilterChain 过滤器的类型是什么
在上面的分析中,我们可以看到Filter的创建时通过WebSecurity
对象的build方法完成的。我们先看一下WebSecurity对象的部分文档注释:
这段话大致的意思是,WebSecurity由WebSecurityConfiguration创建,而WebSecurity的作用是用于创建一个类型为FilterChainProxy
的过滤器,FilterChainProxy是Filter的子类,我们所说的创建一个名字为springSecurityFilterChain的过滤器,实际上过滤器的具体类型就是FilterChainProxy。
到这里实际上我们实际上对于两个答案都已经有了回答。以下部分的源码分析是为喜欢刨根问底的用户专门提供的,用于讲解FilterChainProxy的构建过程,如果读者感觉有难度,可以直接跳过,继续阅读过滤器注册过程的源码分析。
在继续分析源码之前,我们需要提示一下,WebSecurity继承了AbstractConfiguredSecurityBuilder
,而AbstractConfiguredSecurityBuilder又继承了AbstractSecurityBuilder
对象。
我们来具体的看一下WebSecuirty对象build方法的实现,这个方法定义在AbstractSecurityBuilder中,源码如下:
public final O build() throws Exception { if (building.compareAndSet(false, true)) { object = doBuild(); return object; } throw new AlreadyBuiltException("This object has already been built"); }
在这个方法中又调用了doBuild
方法,这个方法是一个抽象方法,在AbstractConfiguredSecurityBuilder类中实现:
@Override protected final O doBuild() throws Exception { synchronized (configurers) { buildState = BuildState.INITIALIZING; beforeInit(); init(); buildState = BuildState.CONFIGURING; beforeConfigure(); configure(); buildState = BuildState.BUILDING; O result = performBuild();//构建返回结果 buildState = BuildState.BUILT; return result; } }
在这个方法中,我们需要关注的是performBuild
方法,因为这个方法最终返回了结果,这又是一个抽象方法,具体的实现定义在WebSecurity对象中。如下:
@Override protected Filter performBuild() throws Exception { Assert.state( !securityFilterChainBuilders.isEmpty(), "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly"); int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size(); List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>( chainSize); for (RequestMatcher ignoredRequest : ignoredRequests) { securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); } for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) { securityFilterChains.add(securityFilterChainBuilder.build()); } //创建FilterChainProxy 对象 FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); if (httpFirewall != null) { filterChainProxy.setFirewall(httpFirewall); } filterChainProxy.afterPropertiesSet(); Filter result = filterChainProxy; if (debugEnabled) { logger.warn("\n\n" + "********************************************************************\n" + "********** Security debugging is enabled. *************\n" + "********** This may include sensitive information. *************\n" + "********** Do not use in a production system! *************\n" + "********************************************************************\n\n"); result = new DebugFilter(filterChainProxy); } postBuildAction.run(); return result; }
可以看到最终返回的Filter类型,的确是FilterChainProxy对象。
过滤器注册过程的源码分析
笔者以读者在项目中使用了Spring MVC的情况进行源码分析讲解注册流程。
在servlet3.0规范中,提供了一个javax.servlet.ServletContainerInitializer接口
,来帮助我们做web应用的初始化操作,例如动态注册Servelt、Filter、Listener等。定义如下:
package javax.servlet; import java.util.Set; public interface ServletContainerInitializer { public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException; }
假设我们的项目中使用了SpringMVC,那么肯定会jar依赖spring-web-x.x.x.jar。在这个jar中,我们可以找到以下的文件/META-INF/services/javax.servlet.ServletContainerInitializer。以笔者的依赖为例,这个文件中的内容如下:
根据servlet3.0规范,servlet容器要负责创建这个文件中定义的类,在这里就是SpringServletContainerInitializer
类,其需要是ServletContainerInitializer的实现,因此其必然实现了onStart
()方法,源码如下:
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... //对于非接口、非抽象类的class对象,通过反射创建实例 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) waiClass.newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } AnnotationAwareOrderComparator.sort(initializers); servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers); //调用所有实例的onStart方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
这个类上有一个注解@HandlesTypes(WebApplicationInitializer.class)
,根据Servelt3.0规范,容器要负责以Set集合的方式传递注解中指定的类型的所有子类(包括子接口、实现类等)的class对象。因此WebApplicationInitializer对象的所有子类(包括子接口等)都会在调用SpringServletContainerInitializer类的onStart方法的时候被传入。而Spring Security的AbstractSecurityWebApplicationInitializer类是Spring web提供的WebApplicationInitializer的抽象子类,因此会被传入。而WebApplicationInitializer也是一个接口,其也定义了一个onstart方法,在代码的最后,调用了所有WebApplicationInitializer子类实例的onstart方法。不过由于AbstractSecurityWebApplicationInitializer是抽象类,而代码中if判断逻辑指明了,只有普通的java类才会被创建实例,才有机会被调用start方法,所以我们必须要自定义一个类SecurityWebApplicationInitializer
继承AbstractSecurityWebApplicationInitializer,这样AbstractSecurityWebApplicationInitializer中的onstart方法才有机会被调用。而我们的自动注册就是在这个类的onstart方法中被调用。AbstractSecurityWebApplicationInitializer类核心代码如下:
public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer { ..... public final void onStartup(ServletContext servletContext) throws ServletException { beforeSpringSecurityFilterChain(servletContext); if (configurationClasses != null) { AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(configurationClasses); servletContext.addListener(new ContextLoaderListener(rootAppContext)); } if (enableHttpSessionEventPublisher()) { servletContext .addListener("org.springframework.security.web.session.HttpSessionEventPublisher"); } servletContext.setSessionTrackingModes(getSessionTrackingModes()); insertSpringSecurityFilterChain(servletContext);//完成springSecurityFilterChain的注册 afterSpringSecurityFilterChain(servletContext); } ..... }
上述代码中的insertSpringSecurityFilterChain(servletContext)完成了事实上的Filter的注册。(在此不再继续分析源码,只是讲解流程,以免涉及过多了基础知识,读者本应该掌握这些的)。因为我们之前创建的Filter实际上是Spring Context中的一个Bean,因此当我们在insertSpringSecurityFilterChain方法中传入servletContext的时候,实际上我们就可以根据bean的名称"springSecurityFilterChain"获取到这个Bean。Spring Web会根据创建一个代理的Filter类,DelegatingFilterProxy
,这个类中维护了一个非常重要的字段targetBeanName
,在我们这里这个字段的值是springSecurityFilterChain。而DelegatingFilterProxy在执行的时候,会根据targetBeanName,找到对应的以Spring Bean方式提供的Filter来处理。因此事实上完成了注册流程。
