Spring Security(3):配置與自動配置的介紹及源碼分析

基於註解的配置(Java Configuration)從Spring Security 3.2開始就已經支持,本篇基於Spring boot註解的配置進行講解,若是須要基於XML配置(Security Namespace Configuration),可查閱Spring Security官網:https://docs.spring.io/spring-security/site/docs/5.1.5.RELEASE/reference/htmlsingle/#ns-confightml

基於Maven的Spring及Spring Boot配置再也不贅述,想要配置Spring Security,只須要@EnableWebSecurity註解。若是須要自定義一些配置,則須要和繼承WebSecurityConfigurerAdapter後,覆蓋某些方法java

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter { }

本節主要講經過@EnableWebSecurity的默認配置。下節來說經過繼承WebSecurityConfigurerAdapter的自定義配置。web

 

[2019/06/04 ADD]spring

在SpringBoot中,只要你加入spring-boot-starter-security包到項目中,即便不配置@EnableWebSecurity和WebSecurityConfigurerAdapter,SpringBoot也會自動給咱們添加這兩個配置。具體能夠看SpringBootWebSecurityConfiguration及WebSecurityEnablerConfiguration。安全

/**
 * The default configuration for web security. It relies on Spring Security's
 * content-negotiation strategy to determine what sort of authentication to use. If the
 * user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off
 * completely and the users should specify all the bits that they want to configure as
 * part of the custom security configuration.
 *
 * @author Madhura Bhave
 * @since 2.0.0
 */
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {
    @Configuration
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
    }
}
/**
 * If there is a bean of type WebSecurityConfigurerAdapter, this adds the
 * {@link EnableWebSecurity} annotation. This will make sure that the annotation is
 * present with default security auto-configuration and also if the user adds custom
 * security and forgets to add the annotation. If {@link EnableWebSecurity} has already
 * been added or if a bean with name {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has
 * been configured by the user, this will back-off.
 *
 * @author Madhura Bhave
 * @since 2.0.0
 */
@Configuration
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity public class WebSecurityEnablerConfiguration {
}

 

@EnableWebSecurity雖然只是一個註解,但它實際上作了許多事。下面是叢Spring Security官網摘錄下來的:session

(1) Require authentication to every URL in your application  #在你的應用程序中對每一個URL進行驗證
(2) Generate a login form for you  #爲你生成一個登陸表單
(3) Allow the user with the Username user and the Password password to authenticate with form based authentication  #容許使用用戶名和密碼使用驗證表單進行驗證
(4) Allow the user to logout  #容許用戶登出
(5) CSRF attack prevention  #CSRF attack攻擊防範
(6) Session Fixation protection  #Session Fixation Session保護
(7) Security Header integration  #安全Header集成
 - HTTP Strict Transport Security for secure requests  #嚴格的HTTP傳輸安全
 - X-Content-Type-Options integration
 - Cache Control (can be overridden later by your application to allow caching of your static resources)
 - X-XSS-Protection integration
 - X-Frame-Options integration to help prevent Clickjacking
(8) Integrate with the following Servlet API methods  #如下Servlet API方法集成
 - HttpServletRequest#getRemoteUser()
 - HttpServletRequest.html#getUserPrincipal()
 - HttpServletRequest.html#isUserInRole(java.lang.String)
 - HttpServletRequest.html#login(java.lang.String, java.lang.String)
 - HttpServletRequest.html#logout()

 

這麼多功能是怎麼實現的呢?咱們能夠查看@EnableWebSecurity的源碼,發現該註解會配置3個配置類:app

@EnableWebSecurity -> WebSecurityConfiguration.class,WebMvcSecurityConfiguration.class(condition is DispatcherServlet is present),OAuth2ImportSelector.class(condition is OAuth2ClientConfiguration is present)ide

@EnableWebSecurity -> @EnableGlobalAuthentication -> AuthenticationConfiguration.classspring-boot

 

其中,WebSecurityConfiguration是最主要的配置類。WebMvcSecurityConfiguration,OAuth2ImportSelector這裏再也不介紹。因爲在加載WebSecurityConfiguration的過程當中須要用到AuthenticationConfiguration Bean,因此,節下來咱們只講WebSecurityConfigurationoop

下面是WebSecurityConfiguration類的源碼:

/**
 * Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web
 * based security for Spring Security. It then exports the necessary beans. Customizations
 * can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter}
 * and exposing it as a {@link Configuration} or implementing
 * {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This
 * configuration is imported when using {@link EnableWebSecurity}.
 *
 * @see EnableWebSecurity
 * @see WebSecurity
 *
 * @author Rob Winch
 * @author Keesun Baik
 * @since 3.2
 */
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { }

經過註釋能夠總結爲如下幾點:

(1)建立了WebSecurity及上節講的Security Filter Chain(List<SecurityFilterChain>)的代理對象FilterChainProxy Bean。

(2)建立了其餘一些必要的Bean。

(3)若是須要自定義WebSecurity的一些內容,能夠繼承WebSecurityConfigurerAdapter類或直接實現WebSecurityConfigurer接口,而後在去重寫相應方法。固然要用@Configuration聲明它爲配置類(@EnableWebSecurity中有@Configuration註解,不須要重複添加)。

 

(A)構建WebSecurity

初始化:WebSecurityConfiguration會先執行一個set方法(經過set方法注入的Bean List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers):

@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {

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

    /**
     * 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) // [1.2] 
            throws Exception {
        webSecurity = objectPostProcessor  .postProcess(new WebSecurity(objectPostProcessor)); // [1.4] 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)) {  // [1.3]
                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); // [1.5]
        }
        this.webSecurityConfigurers = webSecurityConfigurers;
    }

    @Bean // [1.1] public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
            ConfigurableListableBeanFactory beanFactory) {
        return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
    }
}

[1.1] 用static先聲明一個autowiredWebSecurityConfigurersIgnoreParents Bean。

[1.2] 這個方法先經過@Value註解經過調用[1.1]的AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()獲取ApplicationContext中全部的WebSecurityConfigurer。具體能夠看一下AutowiredWebSecurityConfigurersIgnoreParents的源碼。

/**
 * A class used to get all the {@link WebSecurityConfigurer} instances from the current
 * {@link ApplicationContext} but ignoring the parent.
 *
 * @author Rob Winch
 *
 */
final class AutowiredWebSecurityConfigurersIgnoreParents {

    private final ConfigurableListableBeanFactory beanFactory;

    public AutowiredWebSecurityConfigurersIgnoreParents(
            ConfigurableListableBeanFactory beanFactory) {
        Assert.notNull(beanFactory, "beanFactory cannot be null");
        this.beanFactory = beanFactory;
    }

    @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;
    }
}

一般狀況下這個WebSecurityConfigurer List只有一個元素,而且就是咱們本身繼承WebSecurityConfigurerAdapter配置的MySecurityConfig。

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter { }

在SpringBoot自動配置的狀況下,若是咱們沒有繼承,則系統默認會使用SpringBootWebSecurityConfiguration的DefaultConfigurerAdapter。

/**
 * The default configuration for web security. It relies on Spring Security's
 * content-negotiation strategy to determine what sort of authentication to use. If the
 * user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off
 * completely and the users should specify all the bits that they want to configure as
 * part of the custom security configuration.
 *
 * @author Madhura Bhave
 * @since 2.0.0
 */ @ConditionalOnClass(WebSecurityConfigurerAdapter.class) // 有這個對象
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) // 可是沒有聲明這個bean
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {

    @Configuration // 聲明一個DefaultConfigurerAdapter的配置Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

    }
}

[1.3] WebSecurityConfigurer若是有多個的狀況下,要對他們的@Order進行檢查,不能有相同的Order。

[1.4][1.5] 初始化WebSecurity,並將SecurityConfigurer(WebSecurityConfigurerAdapter)應用於此SecurityBuilder(WebSecurity),覆蓋徹底相同類的任何SecurityConfigurer。

 

構建:WebSecurity如何被初始化後,就開始構建,下面就是WebSecurityConfiguration中WebSecurity的構建方法,該方法聲明爲一個Bean,返回的其實就是上一節講的Spring Security Filter Chain。

    /**
     * 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();
    }

WebSecurity的構建過程很複雜,大概走了下面幾步流程:

[1.1] 調用AbstractSecurityBuilder.build()方法。

[1.2] 調用AbstractConfiguredSecurityBuilder.doBuild()方法(核心方法)。

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

            beforeInit(); // Do nothing if no child class override it.
            init(); // [1.2.1]

            buildState = BuildState.CONFIGURING;

            beforeConfigure();  // Do nothing if no child class override it.
            configure();  // [1.2.2]

            buildState = BuildState.BUILDING;

            O result = performBuild();  // [1.2.3]

            buildState = BuildState.BUILT;

            return result;
        }
    }

[1.2.1] 調用WebSecurityConfigurerAdapter的init(final WebSecurity web)方法。這裏構建了HttpSecurity對象,把HttpSecurity添加到WebSecurity的securityFilterChainBuilders中(用於構建過濾器鏈)以及有一個共享對象FilterSecurityInterceptor。HttpSecurity的構建下面會重點介紹,這裏先略過。

    public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();  // 構建HttpSecurity對象
        web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
// 把該對象添加到WebSecurity對象中用於接下來[1.2.3]構建過濾器鏈,能夠看下面WebSecurity的
// addSecurityFilterChainBuilder()方法
public void run() { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); }
public final class WebSecurity extends
        AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
        SecurityBuilder<Filter>, ApplicationContextAware {

    private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();

    /**
     * <p>
     * Adds builders to create {@link SecurityFilterChain} instances.
     * </p>
     *
     * <p>
     * Typically this method is invoked automatically within the framework from
     * {@link WebSecurityConfigurerAdapter#init(WebSecurity)}
     * </p>
     *
     * @param securityFilterChainBuilder the builder to use to create the
     * {@link SecurityFilterChain} instances
     * @return the {@link WebSecurity} for further customizations
     */
    public WebSecurity addSecurityFilterChainBuilder(
            SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
        this.securityFilterChainBuilders.add(securityFilterChainBuilder);
        return this;
    }
}

[1.2.2] 調用WebSecurityConfigurerAdapter的configure(WebSecurity web),可是什麼都沒作。咱們能夠經過繼承WebSecurityConfigurerAdapter來覆蓋該方法來自定義配置WebSecurity。

[1.2.3] 調用WebSecurity的performBuild()方法,用[1.2.1]的securityFilterChainBuilders構建過濾器鏈,並交給FilterChainProxy代理,並返回。值得一說的是,FilterChainProxy最終委託給DelegatingFilterProxy來執行,後者也是web.xml的Security配置項(來源於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;
    }

 

(B)構建HttpSecurity

在WebSecurity的構建過程當中,在調用WebSecurityConfigurerAdapter的init(final WebSecurity web)方法時(見上面的[1.2.1] ),調用WebSecurityConfigurerAdapter的getHttp()構建了HttpSecurity對象。

    protected final HttpSecurity getHttp() throws Exception {
        if (http != null) {
            return http;
        }
        // The default strategy for publishing authentication events
        DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
                .postProcess(new DefaultAuthenticationEventPublisher());
        localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

        AuthenticationManager authenticationManager = authenticationManager(); // [2.1]
        authenticationBuilder.parentAuthenticationManager(authenticationManager);
        authenticationBuilder.authenticationEventPublisher(eventPublisher);
// 插入一些共享對象(如UserDetailService,ApplicationContext)用於下面HttpSecurity的構建 Map
<Class<? extends Object>, Object> sharedObjects = createSharedObjects(); http = new HttpSecurity(objectPostProcessor, authenticationBuilder, sharedObjects); if (!disableDefaults) { // @formatter:off http .csrf().and() // [2.2] .addFilter(new WebAsyncManagerIntegrationFilter()) // [2.3] .exceptionHandling().and() // [2.4] .headers().and() // [2.5] .sessionManagement().and() // [2.6] .securityContext().and() // [2.7] .requestCache().and() // [2.8] .anonymous().and() // [2.9] .servletApi().and() // [2.10] .apply(new DefaultLoginPageConfigurer<>()).and() // [2.11] .logout(); // [2.12] // @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; }
    protected void configure(HttpSecurity http) throws Exception {
        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

        http
            .authorizeRequests() // [2.13]
                .anyRequest().authenticated()
                .and()
            .formLogin().and() // [2.14]
            .httpBasic(); // [2.15]
    }

 

[2.1] 這裏實際上使用了配置類AuthenticationConfiguration Bean獲得了一個AuthenticationManager,這個過程當中,系統會自動配置這些認證對象:

ProviderManager -> AuthenticationManager

DaoAuthenticationProvider -> AuthenticationProvider

InMemoryUserDetailsManager -> UserDetailsService

User -> MutableUser -> MutableUserDetails -> UserDetails

其中,MutableUser代理了User對象及一個臨時的password。系統會自動生成1個MutableUser,name爲user(無ROLE)。

具體細節能夠看(C)部分。

[2.2] 添加配置器CsrfConfigurer(包含過濾器CsrfFilter *)對CSRF的支持。

[2.3] 添加過濾器WebAsyncManagerIntegrationFilter。

[2.4] 添加配置器ExceptionHandlingConfigurer(包含過濾器ExceptionTranslationFilter *)對異常處理的支持。

[2.5] 添加配置器HeadersConfigurer(包含過濾器HeaderWriterFilter *)支持Adds the Security HTTP headers to the response。

[2.6] 添加配置器SessionManagementConfigurer(包含過濾器SessionManagementFilter *)支持session管理。

[2.7] 添加配置器SecurityContextConfigurer(包含過濾器SecurityContextPersistenceFilter *)支持對SecurityContextHolder的配置。

[2.8] 添加配置器RequestCacheConfigurer(包含過濾器RequestCacheAwareFilter *)支持request cache。

[2.9] 添加配置器AnonymousConfigurer(包含過濾器AnonymousAuthenticationFilter *)支持Anonymous authentication。

[2.10] 添加配置器ServletApiConfigurer(包含過濾器SecurityContextHolderAwareRequestFilter *)支持更多Servlet API。

[2.11] 添加配置器DefaultLoginPageConfigurer(包含過濾器DefaultLoginPageGeneratingFilter,DefaultLogoutPageGeneratingFilter *)支持默認的login和logout。

[2.12] 添加配置器LogoutConfigurer(包含過濾器LogoutFilter *)支持logout。

[2.13] 添加配置器ExpressionUrlAuthorizationConfigurer -> AbstractInterceptUrlConfigurer(包含過濾器FilterSecurityInterceptor *)支持URL based authorization。

[2.14] 添加配置器FormLoginConfigurer -> AbstractAuthenticationFilterConfigurer(包含過濾器UsernamePasswordAuthenticationFilter *)支持經過login認證。

[2.15] 添加配置器HttpBasicConfigurer(包含過濾器BasicAuthenticationFilter *)支持HTTP basic based authentication。

[*] 該過濾器在[1.2.3]中securityFilterChainBuilder.build()時經過調用該配置器的configure()方法把過濾器加到HttpSecurity中。

以上的15個過濾器就和章節2Spring Security(2):過濾器鏈(filter chain)的介紹中的15個過濾器一一對應。

 

(C)構建AuthenticationManager及自動配置時自動建立認證對象

[2.1]可知,Spring Security會自動建立一些認證對象。那麼它們是怎麼建立出來的呢?

在[2.1]中,調用了WebSecurityConfigurerAdapter.authenticationManager()方法。從下面的代碼能夠看到,因爲咱們並未配置自定義的AuthenticationManagerBuilder(變量名是localConfigureAuthenticationBldr),因此咱們用注入的AuthenticationConfiguration,調用AuthenticationConfiguration的getAuthenticationManager()方法,獲得了AuthenticationManager對象。

WebSecurityConfigurerAdapter.authenticationManager():

    private AuthenticationConfiguration authenticationConfiguration;

    /**
     * Gets the {@link AuthenticationManager} to use. The default strategy is if
     * {@link #configure(AuthenticationManagerBuilder)} method is overridden to use the
     * {@link AuthenticationManagerBuilder} that was passed in. Otherwise, autowire the
     * {@link AuthenticationManager} by type.
     *
     * @return the {@link AuthenticationManager} to use
     * @throws Exception
     */
    protected AuthenticationManager authenticationManager() throws Exception {
        if (!authenticationManagerInitialized) {
            configure(localConfigureAuthenticationBldr);
            if (disableLocalConfigureAuthenticationBldr) {
                authenticationManager = authenticationConfiguration .getAuthenticationManager(); // execute here
            }
            else {
                authenticationManager = localConfigureAuthenticationBldr.build();
            }
            authenticationManagerInitialized = true;
        }
        return authenticationManager;
    }

    @Autowired public void setAuthenticationConfiguration(
            AuthenticationConfiguration authenticationConfiguration) {
        this.authenticationConfiguration = authenticationConfiguration;
    }

AuthenticationConfiguration.getAuthenticationManager()

    public AuthenticationManager getAuthenticationManager() throws Exception {
        if (this.authenticationManagerInitialized) {
            return this.authenticationManager;
        }
// [3.1] AuthenticationManagerBuilder authBuilder
= authenticationManagerBuilder( this.objectPostProcessor, this.applicationContext); if (this.buildingAuthenticationManager.getAndSet(true)) { return new AuthenticationManagerDelegator(authBuilder); }
// [3.2]
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) { authBuilder.apply(config); }
// [3.3] authenticationManager
= authBuilder.build(); if (authenticationManager == null) { authenticationManager = getAuthenticationManagerBean(); } this.authenticationManagerInitialized = true; return authenticationManager; }

 

[3.1] 調用AuthenticationConfiguration.authenticationManagerBuilder()方法,使用了一個默認的AuthenticationManagerBuilder實現類DefaultPasswordEncoderAuthenticationManagerBuilder(同時這也是一個Bean)。

    @Bean
    public AuthenticationManagerBuilder authenticationManagerBuilder(
            ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
        LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
        AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);

        DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
        if (authenticationEventPublisher != null) {
            result.authenticationEventPublisher(authenticationEventPublisher);
        }
        return result;
    }

 

[3.2] 這個globalAuthConfigurers其實就是AuthenticationConfiguration中聲明的3個static bean。因爲是static的,因此最先加載。

    @Bean
    public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
            ApplicationContext context) {return new EnableGlobalAuthenticationAutowiredConfigurer(context);
    }

    @Bean
    public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {return new InitializeUserDetailsBeanManagerConfigurer(context);
    }

    @Bean
    public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
    }

 

[3.3] build()方法會調用AbstractConfiguredSecurityBuilder.doBuild()方法,最終會前後調用[3.2]的3個configurer的init()方法和configure()方法,及調用[3.1]DefaultPasswordEncoderAuthenticationManagerBuilder的父類AuthenticationManagerBuilder的performBuild()方法。

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

            beforeInit();
// 循環調用[3.2]的3個configurer的init()方法(有些可能沒有) init(); buildState
= BuildState.CONFIGURING; beforeConfigure();
// 循環調用[3.2]的3個configurer的configure()方法(有些可能沒有) configure(); buildState
= BuildState.BUILDING; // 調用[3.1]DefaultPasswordEncoderAuthenticationManagerBuilder的父類AuthenticationManagerBuilder的performBuild()方法 O result = performBuild(); buildState = BuildState.BUILT; return result; } }

 

經過調用這些方法自動生成了:

ProviderManager -> AuthenticationManager

DaoAuthenticationProvider -> AuthenticationProvider

InMemoryUserDetailsManager -> UserDetailsService

User -> MutableUser -> MutableUserDetails -> UserDetails

 

(C.1)User & InMemoryUserDetailsManager & DaoAuthenticationProvider:在InitializeUserDetailsBeanManagerConfigurer.config()中,及自動配置類UserDetailsServiceAutoConfiguration中建立

InitializeUserDetailsBeanManagerConfigurer:

        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            if (auth.isConfigured()) {
                return;
            }
// 若是使用了Spring Boot, 執行這一步時會使用自動配置,
// 從UserDetailsServiceAutoConfiguration中Lazy load一個InMemoryUserDetailsManager UserDetailsService userDetailsService
= getBeanOrNull( UserDetailsService.class); if (userDetailsService == null) { return; } PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); // 建立DaoAuthenticationProvider,並把UserDetailsService放入其中 DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); if (passwordEncoder != null) { provider.setPasswordEncoder(passwordEncoder); } if (passwordManager != null) { provider.setUserDetailsPasswordService(passwordManager); } provider.afterPropertiesSet(); auth.authenticationProvider(provider); }

UserDetailsServiceAutoConfiguration:須要注意的是,Spring Bean容器中,若是同時沒有AuthenticationManager,AuthenticationProvider,UserDetailsService時,該自動配置纔會生效。(To also switch off the UserDetailsService configuration, you can add a bean of type UserDetailsService, AuthenticationProvider, or AuthenticationManager.)

@Configuration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class }) public class UserDetailsServiceAutoConfiguration {

    private static final String NOOP_PASSWORD_PREFIX = "{noop}";

    private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern
            .compile("^\\{.+}.*$");

    private static final Log logger = LogFactory
            .getLog(UserDetailsServiceAutoConfiguration.class);

    @Bean
    @ConditionalOnMissingBean(type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
    @Lazy
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(
            SecurityProperties properties,
            ObjectProvider<PasswordEncoder> passwordEncoder) {
        SecurityProperties.User user = properties.getUser();
        List<String> roles = user.getRoles();
        return new InMemoryUserDetailsManager(User.withUsername(user.getName()) .password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())) .roles(StringUtils.toStringArray(roles)).build());
    }

    private String getOrDeducePassword(SecurityProperties.User user,
            PasswordEncoder encoder) {
        String password = user.getPassword();
        if (user.isPasswordGenerated()) {
            logger.info(String.format("%n%nUsing generated security password: %s%n",
                    user.getPassword()));
        }
        if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
            return password;
        }
        return NOOP_PASSWORD_PREFIX + password;
    }

}

 

(C.2)ProviderManager :AuthenticationManagerBuilder.performBuild()中建立

    @Override
    protected ProviderManager performBuild() throws Exception {
        if (!isConfigured()) {
            logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
            return null;
        }
        ProviderManager providerManager = new ProviderManager(authenticationProviders, parentAuthenticationManager);
        if (eraseCredentials != null) {
            providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
        }
        if (eventPublisher != null) {
            providerManager.setAuthenticationEventPublisher(eventPublisher);
        }
        providerManager = postProcess(providerManager);
        return providerManager;
    }

 

 總結:

 最後上兩張類圖,分別是SecurityBuilder和SecurityConfiger。流程實際上就是先調用Builder的add()方法或apply()方法添加和維護一個SecurityConfiger List。最後經過調用Builder的build()方法(其實是AbstractConfiguredSecurityBuilder的doBuild()方法),調用SecurityConfiger的init()方法和configure()方法構建WebSecurity及過濾器鏈。

相關文章
相關標籤/搜索