spring security是通过一个过滤器链来保护你的web应用安全。在spring security中,该过滤链的名称为springSecurityFilterChain,类型为FilterChainProxy。并通过DelegatingFilterProxy代理调用。对于这一点,这样说可能更好理解:springSecurityFilterChain是spring中的bean,而过滤器要想起作用必须配置在web.xml中。为了使springSecurityFilterChain起到拦截作用,就必须让web.xml意识到(其实应该是说让servlet容器/Tomcat)。而DelegatingFilterChain就起到了该角色的作用。它将web.xml和springSecurityFilterChain联系起来。接下来,我们用spring-security-4 (2)spring security 基于Java配置的搭建中的代码为例,来讲解spring security过滤器的创建和注册原理。
一、Spring Security过滤器的创建原理
让我们首先看下MySecurityConfig类
@EnableWebSecurity @Configuration public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Autowired public void configUser(AuthenticationManagerBuilder builder) throws Exception { builder .inMemoryAuthentication() //创建用户名为user,密码为password的用户 .withUser("user").password("password").roles("USER"); } }
可以看到MySecurityConfig上的@EnableWebSecurity注解,查看该注解的源码
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.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; }
@EnableWebSecurity上的@Import注解引入了两个类WebSecurityConfiguration和SpringWebMvcImportSelector,spring security的过滤器正是由WebSecurityConfiguration创建。让我们看下WebSecurityConfiguration的部分源码
... //查看AbstractSecurityWebApplicationInitializer的源码可以看到 //AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME = "springSecurityFilterChain" @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty(); //如果没有配置类那么就new一个WebSecurityConfigurerAdapter,也就是说我们没有配置MySecurityConfig或者说其没有被spring扫描到 if (!hasConfigurers) { WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor .postProcess(new WebSecurityConfigurerAdapter() { }); webSecurity.apply(adapter); } //创建Filter return webSecurity.build(); } ...
从源码中可以看到通过WebSecurity.build()创建出名字为springSecurityFilterChain的Filter对象。(特别说明一下,一定要保证我们的MySecurityConfig类注解了@Configuration并可以被spring扫描到,如果没有被sping扫描到,那么spring security会认为没有配置类,就会新new 出一个WebSecurityConfigureAdapter对象,这会导致我们配置的用户名和密码失效。)那么该Filter的类型是什么呢?别着急,我们先来看下WeSecurity的继承体系。
build方法定义在AbstractSecurityBuilder中,源码如下:
... public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { //通过doBuild方法创建 this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); } ...
doBuild方法定义在AbstractConfiguredSecurityBuilder中,源码如下:
... protected final O doBuild() throws Exception { synchronized (configurers) { buildState = BuildState.INITIALIZING; beforeInit(); init(); buildState = BuildState.CONFIGURING; beforeConfigure(); configure(); buildState = BuildState.BUILDING; //performBuild方法创建 O result = performBuild(); buildState = BuildState.BUILT; return result; } } ...
performBuild()方法定义在WebSecurity中,源码如下
... 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(" " + "******************************************************************** " + "********** Security debugging is enabled. ************* " + "********** This may include sensitive information. ************* " + "********** Do not use in a production system! ************* " + "******************************************************************** "); result = new DebugFilter(filterChainProxy); } postBuildAction.run(); return result; } ...
不关心其具体实现,我们从源码中看到spring security创建的过滤器类型为FilterChainProxy。由此完成过滤器的创建。
二、Spring Security过滤器的注册原理
看下我们创建的SecurityInitializer类:
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer { }
这段代码虽然很简单,但却是注册过滤器所必须的。
根据Servlet3.0中,提供了ServletContainerInitializer接口,该接口提供了一个onStartup方法,用于在容器启动时动态注册Servlet,Filter,Listener等。因为我们建立的是web项目,那我们的依赖中肯定是由spring-web依赖的
根据Servlet 3.0规范,Servlet容器在启动时,会负责创建图中红色箭头所指的类,即SpringServletContainerInitializer,该类是ServletContainerInitializer的实现类。那么该类必有onStartup方法。让我们看下它的源码
package org.springframework.web; import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; import java.util.ServiceLoader; import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @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) { //如果waiClass不为接口,抽象类,并且属于WebApplicationInitializer类型 //那么通过反射构造该接口的实例。 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); for (WebApplicationInitializer initializer : initializers) { //调用所有WebApplicationInitializer实例的onStartup方法 initializer.onStartup(servletContext); } } }
请注意该类上的@HandlesTypes(WebApplicationInitializer.class)注解,根据Sevlet3.0规范,Servlet容器要负责以Set集合的方式注入指定类的子类(包括接口,抽象类)。其中AbstractSecurityWebApplicationInitializer是WebApplicationInitializer的抽象子类,我我们看下它的onStartup方法
... public final void onStartup(ServletContext servletContext) throws ServletException { 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(servletContext)就是在注册过滤器。因为在过滤器创建中所说的springSecurityFilterChain,它其实是spring中的bean,而servletContext也必定可以获取到该bean。我们接着看insertSpringSecurityFilterChain的源码
... public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain"; private void insertSpringSecurityFilterChain(ServletContext servletContext) { String filterName = DEFAULT_FILTER_NAME; //通过DelegatingFilterProxy代理 DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy( filterName); String contextAttribute = getWebApplicationContextAttribute(); if (contextAttribute != null) { springSecurityFilterChain.setContextAttribute(contextAttribute); } //完成过滤器的注册 registerFilter(servletContext, true, filterName, springSecurityFilterChain); } ...
一开始我们就提到了调用过滤器链springSecurityFilterChain需要DelegatingFilterProxy进行代理,将其与web.xml联系起来。这段代码就是很好的证明。DelegatingFilterProxy中维护了一个类型为String,名字叫做targetBeanName的字段,targetBeanName就是DelegatingFilterProxy所代理的类的名称。最后通过registerFilter最终完成过滤器的注册。
参考资料:http://www.tianshouzhi.com/api/tutorials/spring_security_4/250
https://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/reference/htmlsingle/