3.2 创建与注册过滤器流程的源码分析

2016-06-11 01:52:39 14,719 10

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对象的部分文档注释:

Image.png

这段话大致的意思是,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。以笔者的依赖为例,这个文件中的内容如下:

Image.png

根据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来处理。因此事实上完成了注册流程。