spring-security-4 (3)spring security過濾器的建立與註冊原理

   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

1、Spring Security過濾器的建立原理

  讓咱們首先看下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

2、Spring Security過濾器的註冊原理

  看下咱們建立的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/

相關文章
相關標籤/搜索