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過濾器的建立和註冊原理。html
讓咱們首先看下MySecurityConfig類java
@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註解,查看該註解的源碼web
@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的部分源碼spring
... //查看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的繼承體系。api
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中,源碼以下:session
... 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中,源碼以下app
... 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。由此完成過濾器的建立。ide
看下咱們建立的SecurityInitializer類:post
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/