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("\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最終完成過濾器的注冊。
參考資料:http://www.tianshouzhi.com/api/tutorials/spring_security_4/250
https://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/reference/htmlsingle/