Spring Security 源码分析九:Java config - AuthenticationManagerBuilder

栏目: 后端 · 发布时间: 5年前

内容简介:本文是对 Spring Security Core 4.0.4 Release 进行源码分析的系列文章之一;本系列开始,将讲解有关 Spring Security 的配置相关的内容;本文为作者的原创作品,转载需注明出处;

本文是对 Spring Security Core 4.0.4 Release 进行源码分析的系列文章之一;

本系列开始,将讲解有关 Spring Security 的配置相关的内容;

本文为作者的原创作品,转载需注明出处;

简介

该篇文章是继上一篇博文 Spring Security 源码分析九:Java config - HttpSecurity & SecurityConfigurer 的延续,上篇博文中,笔者止步于 UserDetailsAwareConfigurer 小节,简单介绍了一下 UserDetailsAwareConfigurer 该配置类实例是 AuthenticationManagerBuilder 实例的 configures,辅助其进行构建操作;

其构建过程等价于 RestSecurityConfig、WebSecurityConfig 之于 WebSecurity;FormLoginConfigurer、HttpBasicConfigurer 之于 HttpSecurity;

回顾

先来回顾一下什么是 InMemoryUserDetailsManagerJdbcUserDetailsManager ,回顾一下 Spring Security 源码分析三:Core Services 核心服务 中的

Spring Security 源码分析九:Java config - AuthenticationManagerBuilder

从类图中可以清晰的看到,JdbcUserDetailsManager、InMemoryUserDetailsManager 均是 DaoAuthenticationProvider 的重要属性,提供了如何管理用户的方式, JdbcUserDetailsManager 提供了 Jdbc 的方式来 用户信息,而InMemoryUserDetailsManager则是提供了内存的方式;

而后面我们将看到 InMemoryUserDetailsManagerConfigurerJdbcUserDetailsManagerConfigurer 均是封装了 InMemoryUserDetailsManagerJdbcUserDetailsManager 来分别以 内存 的方式或者是以 数据库 的方式来 用户信息;

类图

SecurityConfigurer

SecurityConfigurer Overall

下面这张类图正是前叙文章中所介绍到的有关 SecurityConfigurer 的全景图;

Spring Security 源码分析九:Java config - AuthenticationManagerBuilder

这张图中可以清晰的看到 AuthenticationManagerBuilder 与 InMemoryUserDetailsManagerConfigurerJdbcUserDetailsManagerConfigurer 之间的关系,它们之间是包含与被包含的关系;并在 AuthenticationManagerBuilder 构建过程中使用到这些 configures;

UserDetailsAwareConfigurer

Spring Security 源码分析九:Java config - AuthenticationManagerBuilder

笔者就 UserDetailsAwareConfigurer 分支做进一步分析;该类图中比较核心的是AbstractDaoAuthenticationConfigurer;通过其扩展出比较典型的实体类InMemoryUserDetailsManagerConfigurer和DaoAuthenticationConfigurer;

AbstractDaoAuthenticationConfigurer

该方法最核心的是两属性和两方法,首先,来看看这两个非常重要的属性,

  • provider

    private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    

    provider -> DaoAuthenticationProvider

  • userDetailsService
    从中可以看到,DaoAuthenticationProvider 与 UserDetailsService 是包含关系,既是 UserDetailsService 对象是 DaoAuthenticationProvider 的一个属性;UserDetailsService 由 UserDetailsManager 接口实现,其实现类可以是 InMemoryUserDetailsManagerConfigurer 或者是 JdbcUserDetailsManagerConfigurer

然后,来看看这两个重要的方法

  • 构造方法

    protected AbstractDaoAuthenticationConfigurer(U userDetailsService) {
       this.userDetailsService = userDetailsService;
       provider.setUserDetailsService(userDetailsService);
    }
    

    上面的构造方法中的逻辑非常的重要;首先,通过构造函数参数 userDetailsService 将其传值给 this.userDetailsService,然后,将 userDetailsService 赋值给 provider;也就是将 InMemoryUserDetailsManager 或者是 JdbcUserDetailsManager 对象赋值给 provider;

  • configure(B builder)

    @Override
    public void configure(B builder) throws Exception {
       provider = postProcess(provider);
       builder.authenticationProvider(provider);
    }
    

    这里的泛型 B 指的就是 AuthenticationManagerBuilder 对象,configure 的核心逻辑就是将 provider 赋值给 builder;

InMemoryUserDetailsManagerConfigurer

public class InMemoryUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
      extends UserDetailsManagerConfigurer<B, InMemoryUserDetailsManagerConfigurer<B>> {

   /**
    * Creates a new instance
    */
   public InMemoryUserDetailsManagerConfigurer() {
      super(new InMemoryUserDetailsManager(new ArrayList<UserDetails>()));
   }
}

InMemoryUserDetailsManagerConfigurer 的实现逻辑非常的简单,就是通过构造函数创建 InMemoryUserDetailsManager 对象,并通过 super 调用到 AbstractDaoAuthenticationConfigurer 的构造函数,正如在AbstractDaoAuthenticationConfigurer中分析的那样,该构造函数中将 InMemoryUserDetailsManager 作为 userDetailsService 参数赋值给了 provider 对象;

DaoAuthenticationConfigurer

public class DaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService>
      extends
      AbstractDaoAuthenticationConfigurer<B, DaoAuthenticationConfigurer<B, U>, U> {

   /**
    * Creates a new instance
    * @param userDetailsService
    */
   public DaoAuthenticationConfigurer(U userDetailsService) {
      super(userDetailsService);
   }
}

从该类的定义中可以知道,可以通过其构造函数传递用户自定义的 UserDetailsService 对象;这样就可以提供用户自定义的 UserDetailsManager 对象了,而不再使用默认的 InMemoryUserDetailsManager 或者 JdbcUserDetailsManager 了;

DaoAuthenticationConfigurer 可以通过 AuthenticationManagerBuilder 的方法 userDetailsService(T userDetailsService) 来进行扩展;

SecurityBuilder

Spring Security 源码分析九:Java config - AuthenticationManagerBuilder

AuthenticationManagerBuilder

由类图可知,AuthenticationManagerBuilder 是一个 SecuirtyBuilder 对象;笔者就核心的属性和方法进行分析如下;

首先,来看看相关的核心属性,

  • AuthenticationManager parentAuthenticationManager
    当通过 performBuild() 方法构建 ProviderManager 的时候,将会使用 parentAuthenticationManager 作为该 ProviderManager 对象的 parent authentication manager;
  • List

    authenticationProviders

    保存用于构造 AuthenticationManager 的 AuthenticationProvider 对象;从章节可知,AuthenticationManager 与 AuthenticationProvider 是 1 对多的关系;

  • Boolean eraseCredentials
    当用户验证结束以后是否需要在 Authentication 对象中将其密码给删掉;

其次,来看看相关的核心方法,

  • inMemoryAuthentication()

    public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
           throws Exception {
       return apply(new InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
    }
    

    提供给用户来定制所需的 UserDetailsManager 既是 UserDetailsService 对象;比如通过如下的方法来进行定制,

    @Configuration
    @EnableWebSecurity
    @Order(1)
    static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     
      @Autowired
      public void configUser(AuthenticationManagerBuilder builder) throws Exception {     
         builder
            .inMemoryAuthentication()
               .withUser("user").password("password").roles("USER").and()  
               .withUser("manager").password("password").roles("MANAGER");
        
      } 
    
      ......
    
    }
    
  • jdbcAuthentication()

    和 inMemoryAuthentication() 方法类似,提供这样的接口供用户来定制相应的 UserDetailsManager 既是 UserDetailsService 对象;

  • userDetailsService(T userDetailsService)

    public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
           T userDetailsService) throws Exception {
       this.defaultUserDetailsService = userDetailsService;
       return apply(new DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T>(userDetailsService));
    }
    

    从该方法中可以直观的看到其目的是为 AuthenticationProvider 提供用户自定义的 UserDetailsService 对象,当内置的 InMemoryUserDetailsManager 或 JdbcUserDetailsManager UserDetailsService 不能满足用户需要的时候,可以使用该方法来进行扩展;该逻辑是通过DaoAuthenticationConfigurer实现的;

构建过程

概述

将这个构建过程的核心内容总结如下,

在整个构建过程中,AuthenticationManagerBuilder 和 AuthenticationManager 都包含两种类型的角色, 全局的局部的 全局的 AuthenticationManagerBuilder 生成 全局的 AuthenticationManager 局部的 AuthenticationManagerBuilder 生成 局部的 AuthenticationManager ;与 HttpSecurity 相关的某一条安全验证链使用的正是由“局部的 AuthenticationManagerBuilder”所生成的“局部的 AuthenticationManager”来对用户的身份进行认证的,只是“全局的 AuthenticationManager”将作为“局部的 AuthenticationManager”的 parent authentication manager,当“局部的 AuthenticationManager”验证失败后,会使用 parent authentication manager;

另外,“局部的 AuthenticationManagerBuilder” 是 HttpSecurity 的一个私有属性,也就是某条安全链所专有的;也就是说,“用户可以为每条不同的安全链自定义不同的 AuthenticationManager 来对用户执行验证操作”;

全局 AuthenticationManagerBuilder 的初始化和定制流程

初始化过程

该初始化流程是通过@EnableWebSecurity 为入口调用 @EnableGlobalAuthentication 注解加载 AuthenticationConfiguration 来加载该 AuthenticationManagerBuilder 对象到 Spring 容器中的;用下面的这张类图来总结这个过程,

Spring Security 源码分析九:Java config - AuthenticationManagerBuilder

从类图的逻辑中我们可以清晰的看到,首先通过@EnableWebSecurity 注解作为入口,调用 @EnableGlobalAuthentication 注解,该注解将会加载 AuthenticationConfiguration 配置类( 通过 @Configuration 注解的类 ),然后通过 @Bean 方法 getAuthentiationManager() 初始化得到 AuthenticationManagerBuilder ,并将其注入到 Spring 容器中, 特别特别需要注意 的是,这里初始化所得到的 AuthenticationManagerBuilder 就是 全局的 AuthenticationManagerBuilder 对象;下面,笔者就一些核心部分源码摘录如下,

___@EnableWebSecurity.jav a___

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class, ObjectPostProcessorConfiguration.class,
      SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

   /**
    * Controls debugging support for Spring Security. Default is false.
    * @return if true, enables debug support with Spring Security
    */
   boolean debug() default false;
}

可以看到,在加载 @EnableWebSecurity 注解的同时会加载 @EnableGlobalAuthentication 注解,

___@EnableGlobalAuthentication.jav a___

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}

可见当在加载 @EnableGlobalAuthentication 注解的时候会通过 @Import 注解加载配置类 AuthenticationConfiguration

AuthenticationConfiguration.java

通过 @Bean 方法 authenticationManagerBuilder(objectPostProcessor) 初始化得到一个 AuthenticationManagerBuilder 实例并以 Spring bean 的形式返回,载入 Spring 容器中,作为 全局的 AuthenticationManagerBuilder 对象;

@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(
      ObjectPostProcessor<Object> objectPostProcessor) {
   return new AuthenticationManagerBuilder(objectPostProcessor);
}

用户自定义方式

那么这里,我们将如何对 全局的 AuthenticationManagerBuilder 对象进行定制?比如设置 UserDetailsManager 既 UserDetailsService 等;很简单,在初始化的过程中直接从 Spring 容器中获取得到 AuthenticationManagerBuilder 对象,然后对其进行自定义即可;好比下面这个例子,为 全局的 AuthenticationManagerBuilder 对象设置 InMemoryUserDetailsManager 对象,然后在 全局的 AuthenticationManagerBuilder 对象执行构建的过程中,将返回一个包含了 InMemoryUserDetailsManager : UserDetailsService 的 AuthenticationProvider 的 AuthenticationManager 对象;

@Configuration
@EnableWebSecurity
@Order(1)
static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
  @Autowired
  public void configUser(AuthenticationManagerBuilder builder) throws Exception {     
     builder
        .inMemoryAuthentication()
           .withUser("user").password("password").roles("USER").and()  
           .withUser("manager").password("password").roles("MANAGER");
    
  } 

  ......

}

更多相关的用户定制化方法参考AuthenticationManagerBuilder部分的相关介绍;

使用 Sequence Diagram 进行归纳总结

上述的相关步骤可以通过下面的 Sequence Diagram 来进行归纳和总结;

Spring Security 源码分析九:Java config - AuthenticationManagerBuilder

构建流程分析

笔者画了如下的一张完整的 sequence diagram 诠释了 全局的 AuthenticationManagerBuilder局部的 AuthenticationManagerBuilder 的构建过程;

Spring Security 源码分析九:Java config - AuthenticationManagerBuilder

该流程图分为四块,分别是左上角、右上角、中间和底部;每个部分对应不同的执行逻辑;笔者就这四个部分僬侥的进行描述如下,

  • 左上角

    此部分既是 Step 1 -> Step 1.1 结束;该部分逻辑既是 局部的 AuthenticationManagerBuilder 的初始化流程;该逻辑是通过 WebSecurityConfigurerAdapter 的 @Autowired 方法 setObjectPostProcessor() 实现的;

    @Autowired
    public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
       this.objectPostProcessor = objectPostProcessor;
    
       authenticationBuilder = new AuthenticationManagerBuilder(objectPostProcessor);
       localConfigureAuthenticationBldr = new AuthenticationManagerBuilder(
              objectPostProcessor) {
          @Override
          public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
             authenticationBuilder.eraseCredentials(eraseCredentials);
             return super.eraseCredentials(eraseCredentials);
          }
    
       };
    }
    

    可见,该方法中通过初始化构建了一个 AuthenticationManagerBuilder 对象并将其赋值给 WebSecurityConfigurerAdapter 的局部变量 authenticationBuilder,也正因为如此,笔者将该对象称作 局部的 AuthenticationManagerBuilder ;后面我们可以看到,它将通过传参的方式,将该 局部的 AuthenticationManagerBuilder 传递给与当前安全作用链相关的 HttpSecurity 对象中;

  • 右上角

    从 Step 2 -> Step 3.2.1.1 结束;该部分主要是相关 SecurityConfigurer 的构建行为,主要描述了 DaoAuthenticationProvider 的生成过程,以及如何将 UserDetailsService 关联到 DaoAuthenticationProvider 对象中的;注意,这里是通过子类 InMemoryUserDetailsManagerConfigurer 的构造函数创建的 UserDetailsService 对象并通过 super 调用父类的构造函数,最终将 UserDetailsService 赋值给 DaoAuthenticationProvider 对象饿;

  • 中间部分

    该部分涵盖了 Step 4 以及相关的所有的子步骤;该部分主要是描述 全局的 AuthenticationManagerBuilder 的构造过程;该步骤是对 WebSecurity 构造步骤 1.1.1.1.4.3.1 的延续,既是从 WebSecurity 的构建过程中调用 HttpSecurity 执行构建的步骤;这里笔者通过上述的时序图对 全局的 AuthenticationManagerBuilder 的构造过程进行详细的诠释;下面笔者就几个核心的步骤进行简要的描述,

    4.1.1

    该步骤试图从 Spring 容器中获得得到该 全局的 AuthenticationManagerBuilder 对象;

    4.1.1.1.1 build process

    从该步骤正式开始对 全局的 AuthenticationManagerBuilder 对象的构建过程;在执行到 configure 的步骤的时候,将会一次调用所有的 configurers,比如 InMemoryUserDetailsManagerConfigurer、JdbcUserDetailsManagerConfigurer 等,的 configure 方法来执行相关的配置操作,步骤 4.1.1.1.1.1.2.1.1 configure(B builder) 中将会把 DaoAuthenticationProvider 对象作为参数注入到 builder 中;

    最后,该步骤执行完以后,将会生成一个 全局的 Authentication Manager 并返回;

    4.1.1.1.1.1.3 perform build process

    该步骤是整个 build 的最后一步,该步骤将会通过 ProviderManager 构造函数构建一个 ProviderManager 对象并返回;

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

    通过 DemoApplication 进行构建的过程,注意这里的参数,authenticationProviders 仅包含一个 DaoAuthenticationProvider 对象;parentAuthenticationManager 为 null

    4.1.2

    正如时序图所注解的那样,这一步非常的关键,它将 全局的 Authentication Manager 作为参数传递给了 局部的 Authenticaiton Manager Builder 对象;目的既是在 局部的 Authenticaiton Manager Builder 对象的构建的过程中,将 全局的 Authentication Manager 作为 parent auth manager 赋值给 局部的 Authenticaiton Manager Builder 对象;

    4.1.3.1

    这一步要注意的是,在构建 HttpSecurity 对象的时候,在其构造方法中,通过 setSharedObject(AuthenticationManagerBuilder.class, localAuthBuilder) 方法,将 局部的 Authenticaiton Manager Builder 对象设置为了当前的 HttpSecurity 的局部共享对象;后续我们将看到,HttpSecurity 正式根据此 局部的 Authenticaiton Manager Builder 对象来执行构建的;

  • 底部

    该部分包含 Step 6 以及相关所有的子步骤;该步骤主要描述了 HttpSecurity 如何通过其 局部的 Authenticaiton Manager Builder 对象来执行构建并生成相应的 AuthenticationManager 的;笔者就相关核心步骤进行简要描述如下,

    6.1.2.1

    从与当前 HttpSecurity 相关的共享对象池中获取得到 局部的 Authenticaiton Manager Builder 对象;准备后续的构建动作;

    6.1.2.2

    使用 局部的 Authenticaiton Manager Builder 对象正式开始执行构建动作;

    6.1.2.2.3.1

    该步骤是 perform build process 的核心步骤,生成 ProviderManager 并返回,这里返回的既是 局部的 Authentication Manager ;以 DemoApplication 中的配置为例,注意这里的参数, authenticationProviders 只包含了一个 AnonymousAuthenticationProvider 对象,看来是一个默认的 AuthenticationProvider 对象; parentAuthenticationManager 既是 全局的 Authentication Manager ,这里为什么能过获取到 全局的 Authentication Manager 参考步骤 4.1.2;

    6.1.2.3

    局部的 Authentication Manager 作为 AuthenticationManager.class 类型设置到 HttpSecurity 私有的共享对象池中;这一步的意义非常之大,也就是说,在某个安全链的验证过程中,使用的是其 局部的 Authentication Manager

最后用简短的几句话来总结一下上述复杂流程的目的,这里 Spring Security 设计者们想要达到的目的是,为每一个 Spring Security 安全链( 既 HttpSecurity )创建不同的 局部的 Authentication Manager ,用于用户身份的安全验证;验证过程中,当 局部的 Authentication Manager 验证失败以后将会使用其 parent Authentication Manager 既是 全局的 Authentication Manager 来进行验证;

由此,我的脑海浮现出了这样一种场景,某一条安全链使用的是基于 内存 的认证管理器对用户进行认证,而另外一条安全链使用的是基于 数据库 的认证管理器对用户进行认证,再有一条安全链使用的是基于 LDAP 的认证管理器对用户进行认证;


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Analytics 2.0

Web Analytics 2.0

Avinash Kaushik / Sybex / 2009-10-26 / USD 39.99

The bestselling book Web Analytics: An Hour A Day was the first book in the analytics space to move beyond clickstream analysis. Web Analytics 2.0 will significantly evolve the approaches from the fir......一起来看看 《Web Analytics 2.0》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

SHA 加密
SHA 加密

SHA 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试