Spring Security 初始化流程詳解

最近在整合微服務OAuth 2認證過程當中,它是基於Spring Security之上,而本人對Spring Security架構原理並不太熟悉,致使不少配置搞不太清楚,遂咬牙啃完了Spring Security核心源碼,花了差很少一星期,整體上來講,其代碼確實比較晦澀,以前在學習Apache Shiro框架以前也曾經在相關論壇裏瞭解過,相比Spring Security,Apache Shiro真的是至關輕量,代碼研讀起來容易不少,而Spring Security類繼承結構複雜,大量使用了其所謂Builder和Configuer模式,其代碼跟蹤過程很痛苦,遂記錄下,分享給有須要的人,因爲本人能力有限,在文章中有不對之處,還請各位執教,在此謝謝各位了。css

本人研讀的Spring Security版本爲:5.1.4.RELEASEjava

Spring Security在3.2版本以後支持Java Configuration,即:經過Java編碼形式配置Spring Security,可再也不依賴XML文件配置,本文采用Java Configuration方式。web

在Spring Security官方文檔中有一個最簡配置例子:spring

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }
}複製代碼

咱們先不要看其它內容,先關注註解@EnableWebSecurity,它是初始化Spring Security的入口,打開其源碼以下:bash

@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;
}複製代碼

該註解類經過@Configuration@Import配合使用引入了一個配置類(WebSecurityConfiguration)和兩個ImportSelector(SpringWebMvcImportSelectorOAuth2ImportSelector),咱們重點關注下WebSecurityConfiguration,它是Spring Security的核心,正是它構建初始化了全部的Bean實例和相關配置,下面咱們詳細分析下。session

打開WebSecurityConfiguration源碼,發現它被@Configuration標記,說明它是配置類,架構

@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware 複製代碼

該類中最重要的工做就是實例並註冊FilterChainProxy,也就是咱們在之前XML文件中配置的過濾器:app

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>複製代碼

該過濾器負責攔截請求,並把請求經過必定的匹配規則(經過RequestMatcher匹配實現)路由(或者Delegate)到具體的SecurityFilterChain,源碼以下:框架

/** * Creates the Spring Security Filter Chain * @return the {@link Filter} that represents the security filter chain * @throws Exception */
    @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();
    }複製代碼

@Bean註解name屬性值AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME就是XML中定義的springSecurityFilterChainide

從源碼中知道過濾器經過最後的webSecurity.build()建立,webSecurity的類型爲:WebSecurity,它在setFilterChainProxySecurityConfigurer方法中優先被建立了:

/** * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} * instances used to create the web configuration. * * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a * {@link WebSecurity} instance * @param webSecurityConfigurers the * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to * create the web configuration * @throws Exception */
    @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);
        }

        Collections.sort(webSecurityConfigurers, 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;
    }複製代碼

從代碼中能夠看到,它是直接被new出來的:

webSecurity = objectPostProcessor
                .postProcess(new WebSecurity(objectPostProcessor));複製代碼

setFilterChainProxySecurityConfigurer方法參數中須要被注入兩個對象:objectPostProcessorwebSecurityConfigurersobjectPostProcessor是在ObjectPostProcessorConfiguration配置類中註冊的,而webSecurityConfigurers則是使用了@Value註解方式,註解內容爲:#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()},經過源碼瞭解,autowiredWebSecurityConfigurersIgnoreParents是在本類中被註冊:

@Bean
    public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents( ConfigurableListableBeanFactory beanFactory) {
        return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
    }複製代碼

在AutowiredWebSecurityConfigurersIgnoreParents中定義了方法:getWebSecurityConfigurers

@SuppressWarnings({ "rawtypes", "unchecked" })
    public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
        List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
        Map<String, WebSecurityConfigurer> beansOfType = beanFactory
                .getBeansOfType(WebSecurityConfigurer.class);
        for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
            webSecurityConfigurers.add(entry.getValue());
        }
        return webSecurityConfigurers;
    }複製代碼

它經過BeanFactory獲取了類型爲WebSecurityConfigurer的Bean實例列表。回到WebSecurityConfiguration類中的setFilterChainProxySecurityConfigurer方法,它把WebSecurityConfigurer列表設置到了WebSecurity中,源碼以下:

for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            webSecurity.apply(webSecurityConfigurer);
        }複製代碼

經過apply方法,apply方法其實就是webSecurityConfigurer放入webSecurity維護的configurers屬性中,configurers是個LinkedHashMap,源碼以下:

/** * Applies a {@link SecurityConfigurer} to this {@link SecurityBuilder} overriding any * {@link SecurityConfigurer} of the exact same class. Note that object hierarchies * are not considered. * * @param configurer * @return the {@link SecurityConfigurerAdapter} for further customizations * @throws Exception */
    public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
        add(configurer);
        return configurer;
    }複製代碼

其中代碼add(configurer)就是將這些webSecurityConfigurer添加到webSecurityconfigurers屬性中。

如今webSecurity的初始化工做已經完成,如今回到springSecurityFilterChain方法中,它首先檢查當前是否配置了webSecurityConfigurer,若是沒有的會默認設置一個,而且調用上面提到的apply方法,源碼以下:

boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }複製代碼

若是已經存在配置了webSecurityConfigurer,則調用webSecurity.build()進行構建。

在進入build方法以前,首先簡單介紹下WebSecurity的繼承結構,
clipboard.png

它實現了SecurityBuilder接口,繼承自AbstractConfiguredSecurityBuilderAbstractConfiguredSecurityBuilder繼承自AbstractSecurityBuilderAbstractSecurityBuilder實現了SecurityBuilder,其中AbstractConfiguredSecurityBuilder實現了經過自定義SecurityConfigurer類來配置SecurityBuilder,上面提到的apply(SecurityConfigurer configurer)就是在該類中實現的,它把configurer保存在它維護的LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>()中。

調用webSecurity.build()後,首先調用的父類AbstractSecurityBuilder中的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");
    }複製代碼

而後調用doBuild()doBuild()在子類中實現,AbstractConfiguredSecurityBuilder實現了該方法:

@Override
    protected final O doBuild() throws Exception {
        synchronized (configurers) {
            buildState = BuildState.INITIALIZING;

            beforeInit();
            init();

            buildState = BuildState.CONFIGURING;

            beforeConfigure();
            configure();

            buildState = BuildState.BUILDING;

            O result = performBuild();

            buildState = BuildState.BUILT;

            return result;
        }
    }複製代碼

在這裏重點關注init()configure()performBuild(),下面逐個分析它們的做用。

init()方法在AbstractConfiguredSecurityBuilder實現:

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);
        }
    }複製代碼

它的工做是迭代調用全部配置的SecurityConfigrerinit方法,在這裏實際上是它的子類WebSecurityConfigurer,由於以前獲取時指定的類型就是WebSecurityConfigurer,在上文中提到AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()中:

Map<String, WebSecurityConfigurer> beansOfType = beanFactory.getBeansOfType(WebSecurityConfigurer.class);複製代碼

而實現了WebSecurityConfigurer接口的就是WebSecurityConfigurerAdapterWebSecurityConfigurerAdapter.init()源碼以下:

public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor = http
                        .getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });
    }複製代碼

它只要完成兩件重要的事情:

  1. 初始化HttpSecurity對象;
  2. 設置HttpSecurity對象添加至WebSecuritysecurityFilterChainBuilders列表中;

初始化HttpSecurity對象在getHttp()方法中實現:

protected final HttpSecurity getHttp() throws Exception {
        if (http != null) {
            return http;
        }

        DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
                .postProcess(new DefaultAuthenticationEventPublisher());
        localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

        AuthenticationManager authenticationManager = authenticationManager();
        authenticationBuilder.parentAuthenticationManager(authenticationManager);
        authenticationBuilder.authenticationEventPublisher(eventPublisher);
        Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();

        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;
    }複製代碼

從代碼中能夠了解,HttpSecurity是直接被new出來的,在建立HttpSecurity以前,首先初始化了AuthenticationManagerBuilder對象,這裏有段代碼很重要就是: AuthenticationManager authenticationManager = authenticationManager();,它建立AuthenticationManager實例,打開authenticationManager()方法:

protected AuthenticationManager authenticationManager() throws Exception {
        if (!authenticationManagerInitialized) {
            configure(localConfigureAuthenticationBldr);
            if (disableLocalConfigureAuthenticationBldr) {
                authenticationManager = authenticationConfiguration
                        .getAuthenticationManager();
            }
            else {
                authenticationManager = localConfigureAuthenticationBldr.build();
            }
            authenticationManagerInitialized = true;
        }
        return authenticationManager;
    }複製代碼

在初始化時,它會調用configure(localConfigureAuthenticationBldr);,默認的實現是:

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        this.disableLocalConfigureAuthenticationBldr = true;
    }複製代碼

【一、個性化配置入口之configure(AuthenticationManagerBuilder auth)
咱們能夠經過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置AuthenticationManager

構建完authenticationManager 實例後,將它設置爲authenticationBuilder的父認證管理器:

authenticationBuilder.parentAuthenticationManager(authenticationManager);複製代碼

並將該authenticationBuilder傳入HttpSecurity構造器構建HttpSecurity實例。

構建完HttpSecurity實例後,默認狀況下會添加默認的攔截其配置:

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();複製代碼

最後調用configure(http);,這又是一個可個性化的配置入口,它的默認實現是:

protected void configure(HttpSecurity http) throws Exception {
        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin().and()
            .httpBasic();
    }複製代碼

默認的配置是攔截全部的請求須要認證以後才能訪問,若是沒有認證,會自動生成一個認證表單要求輸入用戶名和密碼。
【二、個性化配置入口之configure(HttpSecurity http)
咱們能夠經過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置HttpSecurity

OK,目前爲止HttpSecurity已經被初始化,接下去須要設置HttpSecurity對象添加至WebSecuritysecurityFilterChainBuilders列表中:

web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
            public void run() {
                FilterSecurityInterceptor securityInterceptor = http
                        .getSharedObject(FilterSecurityInterceptor.class);
                web.securityInterceptor(securityInterceptor);
            }
        });複製代碼

打開HttpSecurity類結構,和WebSecurity同樣,它也實現了SecurityBuilder接口,一樣繼承自AbstractConfiguredSecurityBuilder

clipboard.png

當全部的WebSecurityConfigurerinit方法被調用以後,webSecurity.init()工做就結束了。
接下去調用了webSecurity.configure(),該方法一樣是在AbstractConfiguredSecurityBuilder中實現的:

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

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

它的主要工做是迭代調用全部WebSecurityConfigurerconfigurer方法,參數是WebSeucrity自己,這又是另一個重要的個性化入口:
【三、個性化配置入口之configure(WebSecurity web)
咱們能夠經過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置WebSecurity

自此,三個重要的個性化入口都已經被調用,即在實現WebSecurityConfigurerAdapter常常須要重寫的:

一、configure(AuthenticationManagerBuilder auth);

二、configure(WebSecurity web);

三、configure(HttpSecurity http);複製代碼

回到webSecurity構建過程,接下去重要的的調用:

O result = performBuild();複製代碼

該方法在WebSecurityConfigurerAdapter中實現,返回的就是過濾器FilterChainProxy,源碼以下:

@Override
    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<>(
                chainSize);
        for (RequestMatcher ignoredRequest : ignoredRequests) {
            securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
        }
        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;
        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;
    }複製代碼

首先計算出chainSize,也就是ignoredRequests.size() + securityFilterChainBuilders.size();,若是你不配置ignoredRequests,那就是securityFilterChainBuilders.size(),也就是HttpSecurity的個數,其本質上就是你一共配置幾個WebSecurityConfigurerAdapter,由於每一個WebSecurityConfigurerAdapter對應一個HttpSecurity,而所謂的ignoredRequests就是FilterChainProxy的請求,默認是沒有的,若是你須要條跳過某些請求不須要認證或受權,能夠以下配置:

@Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/statics/**");
    }複製代碼

在上面配置中,全部以/statics開頭請求都將被FilterChainProxy忽略。

計算完chainSize後,就會建立List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);,遍歷全部的HttpSecurity,調用HtppSecuritybuild()構建其對應的過濾器鏈SecurityFilterChain實例,並將SecurityFilterChain添加到securityFilterChains 列表中:

for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
            securityFilterChains.add(securityFilterChainBuilder.build());
        }複製代碼

調用HtppSecuritybuild()構建其實和調用WebSecuritybuild()構建類相似,父類中方法一次被執行,最後執行自己的performBuild()方法,其源碼以下:

@Override
    protected DefaultSecurityFilterChain performBuild() throws Exception {
        Collections.sort(filters, comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
    }複製代碼

構建SecurityFilterChain主要是完成RequestMatcher和對應的過濾器列表,咱們都知道在Spring Security中,過濾器執行按順序順序的,這個排序就是在performBuild()中完成的,也就是:

Collections.sort(filters, comparator);複製代碼

它經過一個比較器實現了過濾器的排序,這個比較器就是FilterComparator,有興趣的朋友能夠本身去了解詳情。
最後返回的是SecurityFilterChain的默認實現DefaultSecurityFilterChain

構建完全部SecurityFilterChain後,建立最爲重要的FilterChainProxy實例,

FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);複製代碼

構造器中傳入SecurityFilterChain列表,若是開啓了Debug模式,還會被包裝成DebugFilter類型,共開發調試使用,默認是關閉的,能夠經過過下面方式開啓Debug模式:

@Override
    public void configure(WebSecurity web) throws Exception {
        web.debug(true);
    }複製代碼

至此Spring Security 初始化完成,咱們經過繼承WebSecurityConfigurerAdapter來代達到個性化配置目的,文中提到了三個重要的個性化入口,而且WebSecurityConfigurerAdapter是能夠配置多個的,其對應的接口就是會存在多個SecurityFilterChain實例,可是它們人仍然在同一個FilterChainProxy中,經過RequestMatcher來匹配並傳入到對應的SecurityFilterChain中執行請求。

相關文章
相關標籤/搜索