SpringSecurity啓動流程源碼解析 | segmentfault新人第三彈

別辜負生命,別辜負本身。

楔子

前面兩期我講了SpringSecurity認證流程SpringSecurity鑑權流程,今天是第三期,是SpringSecurity的收尾工做,講SpringSecurity的啓動流程。前端

就像不少電影拍火了以後其續做每每是前做的前期故事同樣,我這個第三期要講的SpringSecurity啓動流程也是不擇不扣的"前期故事",它能幫助你真正認清SpringSecurity的總體全貌。java

在以前的文章裏,在說到SpringSecurity中的過濾器鏈的時候,每每是把它做爲一個概念瞭解的,就是咱們只是知道有這麼個東西,也知道它究竟是幹什麼用的,可是咱們殊不知道這個過濾器鏈是由什麼類何時去怎麼樣建立出來的。程序員

今天這期就是要了解SpringSecurity的自動配置到底幫咱們作了什麼,它是如何把過濾器鏈給建立出來的,又是在默認配置的時候怎麼加入了咱們的自定義配置。web

祝有好收穫(邊贊邊看,法力無限)。面試

1. 📚EnableWebSecurity

咱們先來看看咱們通常是如何使用SpringSecurity的。spring

咱們用SpringSecurity的時候都會先新建一個SpringSecurity相關的配置類,用它繼承WebSecurityConfigurerAdapter,而後打上註解@EnableWebSecurity,而後咱們就能夠經過重寫
WebSecurityConfigurerAdapter裏面的方法來完成咱們本身的自定義配置。後端

就像這樣:設計模式

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

    }

}

咱們已經知道,繼承WebSecurityConfigurerAdapter是爲了重寫配置,那這個註解是作了什麼呢?session

從它的名字@EnableWebSecurity咱們能夠大概猜出來,它就是那個幫咱們自動配置了SpringSecurity好心人app

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
        SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.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;
}

emmm,我猜你們應該有註解相關的知識吧,ok,既然大家都有註解相關的知識,我就直接講了。

這個@EnableWebSecurity中有兩個地方是比較重要的:

  • 一是@Import註解導入了三個類,這三個類中的後兩個是SpringSecurity爲了兼容性作的一些東西,兼容SpringMVC,兼容SpringSecurityOAuth2,咱們主要看的實際上是第一個類,導入這個類表明了加載了這個類裏面的內容。
  • 二是@EnableGlobalAuthentication這個註解,@EnableWebSecurity你們還沒搞明白呢,您這又來一個,這個註解呢,其做用也是加載了一個配置類-AuthenticationConfiguration,看它的名字你們也可應該知道它加載的類是什麼相關的了吧,沒錯就是AuthenticationManager相關的配置類,這個咱們能夠之後再說。

綜上所述,@EnableWebSecurity能夠說是幫咱們自動加載了兩個配置類:WebSecurityConfigurationAuthenticationConfiguration(@EnableGlobalAuthentication註解加載了這個配置類)。

其中WebSecurityConfiguration是幫助咱們創建了過濾器鏈的配置類,而AuthenticationConfiguration則是爲咱們注入AuthenticationManager相關的配置類,咱們今天主要講的是WebSecurityConfiguration

2. 📖源碼概覽

既然講的是WebSecurityConfiguration,咱們照例先把源碼給你們看看,精簡了一下可有可無的:

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    private WebSecurity webSecurity;

    private Boolean debugEnabled;

    private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

    private ClassLoader beanClassLoader;

    @Autowired(required = false)
    private ObjectPostProcessor<Object> objectObjectPostProcessor;

    
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }
        return webSecurity.build();
    }


    @Autowired(required = false)
    public void setFilterChainProxySecurityConfigurer(
            ObjectPostProcessor<Object> objectPostProcessor,
            @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
            throws Exception {
        webSecurity = objectPostProcessor
                .postProcess(new WebSecurity(objectPostProcessor));
        if (debugEnabled != null) {
            webSecurity.debug(debugEnabled);
        }

        webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);

        Integer previousOrder = null;
        Object previousConfig = null;
        for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
            Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
            if (previousOrder != null && previousOrder.equals(order)) {
                throw new IllegalStateException(
                        "@Order on WebSecurityConfigurers must be unique. Order of "
                                + order + " was already used on " + previousConfig + ", so it cannot be used on "
                                + config + " too.");
            }
            previousOrder = order;
            previousConfig = config;
        }
        for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            webSecurity.apply(webSecurityConfigurer);
        }
        this.webSecurityConfigurers = webSecurityConfigurers;
    }
    
}

如代碼所示,首先WebSecurityConfiguration是個配置類,類上面打了@Configuration註解,這個註解的做用你們還知道吧,在這裏就是把這個類中全部帶@Bean註解的Bean給實例化一下。

這個類裏面比較重要的就兩個方法:springSecurityFilterChainsetFilterChainProxySecurityConfigurer

springSecurityFilterChain方法上打了@Bean註解,任誰也能看出來就是這個方法建立了springSecurityFilterChain,可是先彆着急,咱們不能先看這個方法,雖然它在上面。

3. 📄SetFilterChainProxySecurityConfigurer

咱們要先看下面的這個方法:setFilterChainProxySecurityConfigurer,爲啥呢?

爲啥呢?

由於它是@Autowired註解,因此它要比springSecurityFilterChain方法優先執行,從系統加載的順序來看,咱們須要先看它。

@Autowired在這裏的做用是爲這個方法自動注入所須要的兩個參數,咱們先來看看這兩個參數:

  • 參數objectPostProcessor是爲了建立WebSecurity實例而注入進來的,先了解一下便可。
  • 參數webSecurityConfigurers是一個List,它其實是全部WebSecurityConfigurerAdapter的子類,那若是咱們定義了自定義的配置類,其實就是把咱們的配置也讀取到了。

    這裏其實有點難懂爲何參數中SecurityConfigurer<Filter, WebSecurity>這個類型能夠拿到WebSecurityConfigurerAdapter的子類?

    由於WebSecurityConfigurerAdapter實現了WebSecurityConfigurer<WebSecurity>接口,而WebSecurityConfigurer<WebSecurity>又繼承了SecurityConfigurer<Filter, T>,通過一層實現,一層繼承關係以後,WebSecurityConfigurerAdapter終於成爲了SecurityConfigurer的子類。

    而參數中SecurityConfigurer<Filter, WebSecurity>中的兩個泛型參數實際上是起到了一個過濾的做用,仔細查看咱們的WebSecurityConfigurerAdapter的實現與繼承關係,你能夠發現咱們的WebSecurityConfigurerAdapter正好是這種類型。

ok,說完了參數,我以爲咱們能夠看看代碼了:

@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
        ObjectPostProcessor<Object> objectPostProcessor,
        @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
        throws Exception {

    // 建立一個webSecurity實例
    webSecurity = objectPostProcessor
            .postProcess(new WebSecurity(objectPostProcessor));
    if (debugEnabled != null) {
        webSecurity.debug(debugEnabled);
    }

    // 根據order排序
    webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);

    Integer previousOrder = null;
    Object previousConfig = null;
    for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
        Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
        if (previousOrder != null && previousOrder.equals(order)) {
            throw new IllegalStateException(
                    "@Order on WebSecurityConfigurers must be unique. Order of "
                            + order + " was already used on " + previousConfig + ", so it cannot be used on "
                            + config + " too.");
        }
        previousOrder = order;
        previousConfig = config;
    }

    // 保存配置
    for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
        webSecurity.apply(webSecurityConfigurer);
    }

    // 成員變量初始化
    this.webSecurityConfigurers = webSecurityConfigurers;
}

根據咱們的註釋,這段代碼作的事情能夠分爲覺得幾步:

  1. 建立了一個webSecurity實例,而且賦值給成員變量。
  2. 緊接着對webSecurityConfigurers經過order進行排序,order是加載順序。
  3. 進行判斷是否有相同order的配置類,若是出現將會直接報錯。
  4. 保存配置,將其放入webSecurity的成員變量中。

你們能夠將這些直接理解爲成員變量的初始化,和加載咱們的配置類配置便可,由於後面的操做都是圍繞它初始化的webSecurity實例和咱們加載的配置類信息來作的。

這些東西還能夠拆出來一步步的來說,可是這樣的話真是一篇文章寫不完,我也沒有那麼大的精力可以事無鉅細的寫出來,我只挑選這條痕跡清晰的主脈絡來說,若是你們看完能明白它的一個加載順序其實就挺好了。

就像Spring的面試題會問SpringBean的加載順序,SpringMVC則會問SpringMVC一個請求的運行過程同樣。

所有弄得明明白白,必需要精研源碼,在初期,咱們只要知道它的一條主脈絡,在以後的使用中,哪出了問題你能夠直接去定位到多是哪有問題,這樣就已經很好了,學習是一個循環漸進的過程。

4. 📃SpringSecurityFilterChain

初始化完變量,加載完配置,咱們要開始建立過濾器鏈了,因此先走setFilterChainProxySecurityConfigurer是有緣由的,若是咱們不把咱們的自定義配置加載進來,建立過濾器鏈的時候怎麼知道哪些過濾器須要哪些過濾器不須要。

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }
        return webSecurity.build();
    }

springSecurityFilterChain方法邏輯就很簡單了,若是咱們沒加載自定義的配置類,它就替咱們加載一個默認的配置類,而後調用這個build方法。

看到這熟悉的方法名稱,你就應該知道這是建造者模式,無論它什麼模式,既然調用了,咱們點進去就是了。

public final O build() throws Exception {
        if (this.building.compareAndSet(false, true)) {
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
    }

build()方法是webSecurity的父類AbstractSecurityBuilder中的方法,這個方法又調用了doBuild()方法。

@Override
protected final O doBuild() throws Exception {
    synchronized (configurers) {
        buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
        
        // 空方法
        beforeInit();
        // 調用init方法
        init();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;

        // 空方法
        beforeConfigure();
        // 調用configure方法
        configure();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;

        // 調用performBuild
        O result = performBuild();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;

        return result;
    }
}

經過個人註釋能夠看到beforeInit()beforeConfigure()都是空方法,
實際有用的只有init()configure()performBuild()方法。

咱們先來看看init()configure()方法。

private void init() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.init((B) this);
        }

        for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
            configurer.init((B) this);
        }
    }

private void configure() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.configure((B) this);
        }
    }

源碼中能夠看到都是先獲取到咱們的配置類信息,而後循環調用配置類本身的init()configure()方法。

前面說過,咱們的配置類是繼承了WebSecurityConfigurerAdapter的子類,而WebSecurityConfigurerAdapter又是SecurityConfigurer的子類,全部SecurityConfigurer的子類都須要實現init()configure()方法。

因此這裏的init()configure()方法其實就是調用WebSecurityConfigurerAdapter本身重寫的init()configure()方法。

其中WebSecurityConfigurerAdapter中的configure()方法是一個空方法,因此咱們只須要去看WebSecurityConfigurerAdapter中的init()方法就行了。

public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
            FilterSecurityInterceptor securityInterceptor = http
                    .getSharedObject(FilterSecurityInterceptor.class);
            web.securityInterceptor(securityInterceptor);
        });
    }

這裏也能夠分爲兩步:

  1. 執行了getHttp()方法,這裏面初始化加入了不少過濾器。
  2. HttpSecurity放入WebSecurity,將FilterSecurityInterceptor放入WebSecurity,就是咱們鑑權那章講過的FilterSecurityInterceptor

那咱們主要看第一步getHttp()方法:

protected final HttpSecurity getHttp() throws Exception {
        http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                sharedObjects);
        if (!disableDefaults) {
            // @formatter:off
            http
                    .csrf().and()
                    .addFilter(new WebAsyncManagerIntegrationFilter())
                    .exceptionHandling().and()
                    .headers().and()
                    .sessionManagement().and()
                    .securityContext().and()
                    .requestCache().and()
                    .anonymous().and()
                    .servletApi().and()
                    .apply(new DefaultLoginPageConfigurer<>()).and()
                    .logout();
            // @formatter:on
            ClassLoader classLoader = this.context.getClassLoader();
            List<AbstractHttpConfigurer> defaultHttpConfigurers =
                    SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

            for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                http.apply(configurer);
            }
        }

        // 咱們通常重寫這個方法
        configure(http);
        return http;
    }

getHttp()方法裏面http調用的那一堆方法都是一個個過濾器,第一個csrf()很明顯就是防止CSRF攻擊的過濾器,下面還有不少,這就是SpringSecurity默認會加入過濾器鏈的那些過濾器了。

其次,還有一個重點就是倒數第二行代碼,我也加上了註釋,咱們通常在咱們自定義的配置類中重寫的就是這個方法,因此咱們的自定義配置就是在這裏生效的。

因此在初始化的過程當中,這個方法會先加載本身默認的配置而後再加載咱們重寫的配置,這樣二者結合起來,就變成了咱們看到的默認配置。(若是咱們不重寫configure(http)方法,它也會一點點的默認配置,你們能夠去看源碼,看了就明白了。)

init()configure()(空方法)結束以後,就是調用performBuild()方法。

protected Filter performBuild() throws Exception {

    int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();

    List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
            chainSize);

    for (RequestMatcher ignoredRequest : ignoredRequests) {
        securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    }

    // 調用securityFilterChainBuilder的build()方法
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
        securityFilterChains.add(securityFilterChainBuilder.build());
    }

    FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);

    if (httpFirewall != null) {
        filterChainProxy.setFirewall(httpFirewall);
    }

    filterChainProxy.afterPropertiesSet();

    Filter result = filterChainProxy;
    
    postBuildAction.run();
    return result;
    }

這個方法主要須要看的是調用securityFilterChainBuilderbuild()方法,這個securityFilterChainBuilder是咱們在init()方法中add的那個,因此這裏的securityFilterChainBuilder其實就是HttpSecurity,因此這裏實際上是調用了HttpSecuritybulid()方法。

又來了,WebSecuritybulid()方法還沒說完,先來了一下HttpSecuritybulid()方法。

HttpSecuritybulid()方法進程和以前的同樣,也是先init()而後configure()最後performBuild()方法,值得一提的是在HttpSecurityperformBuild()方法裏面,會對過濾器鏈中的過濾器進行排序:

@Override
    protected DefaultSecurityFilterChain performBuild() {
        filters.sort(comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
    }

HttpSecuritybulid()方法執行完了以後將DefaultSecurityFilterChain返回給WebSecurityperformBuil()方法,performBuil()方法再將其轉換爲FilterChainProxy,最後WebSecurityperformBuil()方法執行結束,返回一個Filter注入成爲name="springSecurityFilterChain"Bean

通過以上這些步驟以後,springSecurityFilterChain方法執行完畢,咱們的過濾器鏈就建立完成了,SpringSecurity也能夠跑起來了。

後記

看到這的話,其實你已經頗有耐性了,但可能還以爲雲裏霧裏的,由於SpringSecurity(Spring你們族)這種工程化極高的項目項目都是各類設計模式和編碼思想滿天飛,看不懂的時候只能說這什麼玩意,看得懂的時候又該膜拜這是藝術啊。

這些東西它不容易看懂可是比較解耦容易擴展,像一條線下來的代碼就容易看懂可是不容易擴展了,福禍相依。

並且這麼多名稱相近的類名,各類繼承抽象,要好好理解下來的確沒那麼容易,這篇其實想給這個SpringSecurity來個收尾,逼着本身寫的,我這我的喜歡善始善終,這段東西也的確複雜,接下來的幾篇打算寫幾個實用的有意思的也輕鬆的放鬆一下。

若是你對SpringSecurity源碼有興趣能夠跟着來我這個文章,點開你本身的源碼點一點,看一看,加油。

自從上篇徵文發了以後,感受多了不少前端的關注者,掘金果真仍是前端多啊,沒事,雖然我不怎麼寫前端,說不定哪天改行了呢哈哈。

我也不藏着掖着,其實我如今是寫後端的,我對前端呢只能說是略懂略懂,不過無聊了也能夠來看看個人文章,點點贊刷刷閱讀乾乾啥的👍,說不定某一天忽然看懂了某篇文還前端勸退後端入行,加油了你們。

別辜負生命,別辜負本身。

大家的每一個點贊收藏與評論都是對我知識輸出的莫大確定,若是有文中有什麼錯誤或者疑點或者對個人指教均可以在評論區下方留言,一塊兒討論。

我是耳朵,一個一直想作知識輸出的僞文藝程序員,下期見。

相關文章
相關標籤/搜索