Spring Security(3):配置与自动配置的介绍及源码分析

Stella981
• 阅读 1140

基于注解的配置(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-config

基于Maven的Spring及Spring Boot配置不再赘述,想要配置Spring Security,只需要**@EnableWebSecurity注解。如果需要自定义一些配置,则需要和继承WebSecurityConfigurerAdapter后,覆盖某些方法**:

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter { }

本节主要讲通过@EnableWebSecurity的默认配置。下节来讲通过继承WebSecurityConfigurerAdapter的自定义配置。

[2019/06/04 ADD]

在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官网摘录下来的:

(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个配置类:

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

@EnableWebSecurity -> @EnableGlobalAuthentication -> AuthenticationConfiguration.class

其中,WebSecurityConfiguration是最主要的配置类。WebMvcSecurityConfiguration,OAuth2ImportSelector这里不再介绍。由于在加载WebSecurityConfiguration的过程中需要用到AuthenticationConfiguration Bean,所以,节下来我们只讲WebSecurityConfiguration

下面是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)的代理对象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及过滤器链。

Spring Security(3):配置与自动配置的介绍及源码分析

Spring Security(3):配置与自动配置的介绍及源码分析

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这