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();
}
...
特别说明一下,一定要保证我们的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("\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;
}
...
不关心其具体实现,我们从源码中看到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最终完成过滤器的注册。