内容简介:本文是对 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;
回顾
先来回顾一下什么是 InMemoryUserDetailsManager 和 JdbcUserDetailsManager ,回顾一下 Spring Security 源码分析三:Core Services 核心服务 中的
从类图中可以清晰的看到,JdbcUserDetailsManager、InMemoryUserDetailsManager 均是 DaoAuthenticationProvider 的重要属性,提供了如何管理用户的方式, JdbcUserDetailsManager 提供了 Jdbc 的方式来 增
、 删
、 查
、 改
用户信息,而InMemoryUserDetailsManager则是提供了内存的方式;
而后面我们将看到 InMemoryUserDetailsManagerConfigurer 和 JdbcUserDetailsManagerConfigurer 均是封装了 InMemoryUserDetailsManager 和 JdbcUserDetailsManager 来分别以 内存
的方式或者是以 数据库
的方式来 增
、 删
、 查
、 改
用户信息;
类图
SecurityConfigurer
SecurityConfigurer Overall
下面这张类图正是前叙文章中所介绍到的有关 SecurityConfigurer 的全景图;
这张图中可以清晰的看到 AuthenticationManagerBuilder 与 InMemoryUserDetailsManagerConfigurer
、 JdbcUserDetailsManagerConfigurer
之间的关系,它们之间是包含与被包含的关系;并在 AuthenticationManagerBuilder 构建过程中使用到这些 configures;
UserDetailsAwareConfigurer
笔者就 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
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 容器中的;用下面的这张类图来总结这个过程,
从类图的逻辑中我们可以清晰的看到,首先通过@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 来进行归纳和总结;
构建流程分析
笔者画了如下的一张完整的 sequence diagram 诠释了 全局的 AuthenticationManagerBuilder
和 局部的 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
的认证管理器对用户进行认证;
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
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》 这本书的介绍吧!