深入理解 AuthenticationManagerBuilder 【源码篇】

if (eraseCredentials != null) {

providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);

}

if (eventPublisher != null) {

providerManager.setAuthenticationEventPublisher(eventPublisher);

}

providerManager = postProcess(providerManager);

return providerManager;

}

}

  1. 首先,我们可以通过调用 parentAuthenticationManager 方法来给一个 AuthenticationManager 设置 parent。

  2. inMemoryAuthentication、jdbcAuthentication 以及 userDetailsService 几个方法松哥在之前的文章中都已经介绍过了(深入理解 SecurityConfigurer 【源码篇】),作用就是为了配置数据源,这里就不再赘述。

  3. 最后就是 performBuild 方法,这个方法的作用就是根据当前 AuthenticationManagerBuilder 来构建一个 AuthenticationManager 出来,AuthenticationManager 本身是一个接口,它的默认实现是 ProviderManager,所以这里构建的就是 ProviderManager。在构建 ProviderManager 时,一方面传入 authenticationProviders,就是该 ProviderManager 所管理的所有的 AuthenticationProvider,另一方面传入 ProviderManager 的 parent(其实也是一个 ProviderManager)。

整体来说,这段代码还是很好理解的,松哥在之前的文章中和大家介绍过 Spring Security 整合多个数据源,那个时候我们自己配置 ProviderManager,跟这里的方式类似,具体可以参考:Spring Security 可以同时对接多个用户表?

不过自己配置有一个问题就是我们没有配置 ProviderManager 的 parent,没有配置的话,如果当前 ProviderManager 中认证失败的话,就直接抛出失败,而不会去 parent 中再次进行认证了(一般来说也不需要,如果系统比较复杂的话,可能需要)。

AuthenticationManagerBuilder 还有一个实现类叫做 DefaultPasswordEncoderAuthenticationManagerBuilder,作为内部类分别定义在 WebSecurityConfigurerAdapter 和 AuthenticationConfiguration 中,不过 DefaultPasswordEncoderAuthenticationManagerBuilder 的内容比较简单,重写了父类 AuthenticationManagerBuilder 的几个方法,配置了新的 PasswordEncoder,无他,所以这里我就不列出这个的源码了,感兴趣的小伙伴可以自行查看。但是这并不是说 DefaultPasswordEncoderAuthenticationManagerBuilder 就不重要了,因为在后面的使用中,基本上都是使用 DefaultPasswordEncoderAuthenticationManagerBuilder 来构建 AuthenticationManagerBuilder。

好啦,这就是 AuthenticationManagerBuilder。

那么是什么时候通过 AuthenticationManagerBuilder 来构建 AuthenticationManager 的呢?

这就涉及到我们的老熟人 WebSecurityConfigurerAdapter 了。当然,关于 WebSecurityConfigurerAdapter 本身的初始化过程,松哥在后面会专门写文章介绍,今天我们主要来看下如何在 WebSecurityConfigurerAdapter 中开启 AuthenticationManager 的初始化的。

2.1 初始化流程

在初始化流程中,松哥得先和大家介绍一个 AuthenticationConfiguration 类。这个类大家可以当作是一个全局被配类来理解,里边都是一些全局属性的配置:

@Configuration(proxyBeanMethods = false)

@Import(ObjectPostProcessorConfiguration.class)

public class AuthenticationConfiguration {

@Bean

public AuthenticationManagerBuilder authenticationManagerBuilder(

ObjectPostProcessor 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;

}

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

}

public AuthenticationManager getAuthenticationManager() throws Exception {

if (this.authenticationManagerInitialized) {

return this.authenticationManager;

}

AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);

if (this.buildingAuthenticationManager.getAndSet(true)) {

return new AuthenticationManagerDelegator(authBuilder);

}

for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {

authBuilder.apply(config);

}

authenticationManager = authBuilder.build();

if (authenticationManager == null) {

authenticationManager = getAuthenticationManagerBean();

}

this.authenticationManagerInitialized = true;

return authenticationManager;

}

@Autowired

public void setApplicationContext(ApplicationContext applicationContext) {

this.applicationContext = applicationContext;

}

@Autowired

public void setObjectPostProcessor(ObjectPostProcessor objectPostProcessor) {

this.objectPostProcessor = objectPostProcessor;

}

private static class EnableGlobalAuthenticationAutowiredConfigurer extends

GlobalAuthenticationConfigurerAdapter {

private final ApplicationContext context;

private static final Log logger = LogFactory

.getLog(EnableGlobalAuthenticationAutowiredConfigurer.class);

EnableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {

this.context = context;

}

@Override

public void init(AuthenticationManagerBuilder auth) {

Map<String, Object> beansWithAnnotation = context

.getBeansWithAnnotation(EnableGlobalAuthentication.class);

if (logger.isDebugEnabled()) {

logger.debug("Eagerly initializing " + beansWithAnnotation);

}

}

}

}

  1. 这里首先构建了一个 AuthenticationManagerBuilder 实例,这个实例就是用来构建全局 AuthenticationManager 的 AuthenticationManagerBuilder,具体的构建过程在下面的 getAuthenticationManager 方法中。不过这里的这个全局的 AuthenticationManagerBuilder 并非总是有用,为什么这么说呢?且看松哥下面的的分析。

  2. 另外还有一些 initializeXXX 方法,用来构建全局的 UserDetailService 和 AuthenticationProvider,这些方法小伙伴可以作为一个了解,因为正常情况下是不会用到这几个 Bean 的,只有当 getAuthenticationManager 方法被调用时,这些默认的 Bean 才会被配置,而 getAuthenticationManager 方法被调用,意味着我们要使用系统默认配置的 AuthenticationManager 作为 parent,而在实际使用中,我们一般不会使用系统默认配置的 AuthenticationManager 作为 parent,我们自己多多少少都会重新定制一下。

这就是 AuthenticationConfiguration 的主要功能,它主要是提供了一些全局的 Bean,这些全局的 Bean 虽然一定会初始化,但是并非一定用到。

那么到底什么时候用到,什么时候用不到,这就和 WebSecurityConfigurerAdapter 有关了,在 WebSecurityConfigurerAdapter 中有三个重要的方法涉及到 AuthenticationManager 的初始化问题,第一个是 setApplicationContext 方法:

public void setApplicationContext(ApplicationContext context) {

this.context = context;

ObjectPostProcessor objectPostProcessor = context.getBean(ObjectPostProcessor.class);

LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);

authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);

localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {

@Override

public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {

authenticationBuilder.eraseCredentials(eraseCredentials);

return super.eraseCredentials(eraseCredentials);

}

@Override

public AuthenticationManagerBuilder authenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {

authenticationBuilder.authenticationEventPublisher(eventPublisher);

return super.authenticationEventPublisher(eventPublisher);

}

};

}

在该方法中,创建了两个几乎一摸一样的 AuthenticationManagerBuilder 实例,为什么会有两个呢?第一个 authenticationBuilder 是一个局部的 AuthenticationManagerBuilder,将来会传入 HttpSecurity 中去构建局部的 AuthenticationManager;第二个 localConfigureAuthenticationBldr 则是一个用来构建全局 AuthenticationManager 的 AuthenticationManagerBuilder。

有小伙伴会问了,构建全局的 AuthenticationManager 不是一开始就在 AuthenticationConfiguration 中创建了吗?为什么这里还有一个?是的,当前这个 localConfigureAuthenticationBldr 是可以禁用的,如果禁用了,就会使用 AuthenticationConfiguration 中提供的 AuthenticationManagerBuilder,如果没禁用,就使用 localConfigureAuthenticationBldr 来构建全局的 AuthenticationManager。

另一个方法则是 getHttp 方法:

protected final HttpSecurity getHttp() throws Exception {

if (http != null) {

return http;

}

AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();

localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

AuthenticationManager authenticationManager = authenticationManager();

authenticationBuilder.parentAuthenticationManager(authenticationManager);

Map<Class<?>, Object> sharedObjects = createSharedObjects();

http = new HttpSecurity(objectPostProcessor, authenticationBuilder,

sharedObjects);

//省略

return http;

}

在 getHttp 方法中,会首先调用 authenticationManager 方法去获取一个全局的 AuthenticationManager,并设置给 authenticationBuilder 作为 parent,然后在构建 HttpSecurity 时将 authenticationBuilder 传入进去。

那么接下来就是 authenticationManager() 方法到底是怎么执行的了:

protected AuthenticationManager authenticationManager() throws Exception {

if (!authenticationManagerInitialized) {

configure(localConfigureAuthenticationBldr);

if (disableLocalConfigureAuthenticationBldr) {

authenticationManager = authenticationConfiguration

.getAuthenticationManager();

}

else {

authenticationManager = localConfigureAuthenticationBldr.build();

}

authenticationManagerInitialized = true;

}

return authenticationManager;

}

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

this.disableLocalConfigureAuthenticationBldr = true;

}

可以看到,如果 AuthenticationManager 还没初始化,那就先进行初始化。初始化首先调用 configure 方法,默认情况下,configure 方法里边会把 disableLocalConfigureAuthenticationBldr 变量设置为 true,这样接下来就会进入到 if 分支中了。这个 configure 方法不知道大家有没有觉得眼熟?我们在自定义的 SecurityConfig 配置类中,一般都是要重写该方法的,一旦重写了这个方法,那么 disableLocalConfigureAuthenticationBldr 变量就不会变为 true,依然是 false,这样在获取 authenticationManager 的时候就会进入到 else 分支中。

如果进入到 if 分支中,意味着开发者并没有重写 configure 方法,AuthenticationManagerBuilder 就使用默认的,大家可以看到,此时就是调用 authenticationConfiguration.getAuthenticationManager() 方法去获取 AuthenticationManager,也就是一开始我们说的那个全局的配置。

如果开发者重写了 configure 方法,意味着开发者对 AuthenticationManagerBuilder 进行了一些定制,此时就不能继续使用 AuthenticationConfiguration 中配置的默认的的 AuthenticationManager 了,而要根据开发者 的具体配置,调用 localConfigureAuthenticationBldr.build 方法去构建新的 AuthenticationManager。

一言以蔽之,AuthenticationConfiguration 中的配置有没有用上,全看开发者有没有重写 configure(AuthenticationManagerBuilder auth) 方法,重写了,就用 localConfigureAuthenticationBldr 来构建 parent 级别的 AuthenticationManager,没重写,就用 AuthenticationConfiguration 中的方法来构建。

这是扮演 parent 角色的 AuthenticationManager 的构建过程,当然,parent 并非必须,如果你没有这个需求的话,也可以不配置 parent。

最后我们再来看下局部的 AuthenticationManager 是如何构建的,也就是和 HttpSecurity 绑定的那个 AuthenticationManager。

根据前面的介绍,HttpSecurity 在构建的时候就会传入 AuthenticationManagerBuilder,如下:

public HttpSecurity(ObjectPostProcessor objectPostProcessor,

AuthenticationManagerBuilder authenticationBuilder,

Map<Class<?>, Object> sharedObjects) {

super(objectPostProcessor);

Assert.notNull(authenticationBuilder, “authenticationBuilder cannot be null”);

setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);

//省略

}

传入进来的 AuthenticationManagerBuilder ,二话不说就存到 SharedObject 里边去了,这个根据官方的注释,说它是一个在不同 Configurer 中共享的对象的工具,其实你可以理解为一个缓存,现在存进去,需要的时候再取出来。

取出来的方法,在 HttpSecurity 中也定义好了,如下:

private AuthenticationManagerBuilder getAuthenticationRegistry() {

return getSharedObject(AuthenticationManagerBuilder.class);

}

在 HttpSecurity 中,凡是涉及到 AuthenticationManager 配置的,都会调用到 getAuthenticationRegistry 方法,如下:

public HttpSecurity userDetailsService(UserDetailsService userDetailsService)

throws Exception {

getAuthenticationRegistry().userDetailsService(userDetailsService);

return this;

}

public HttpSecurity authenticationProvider(

AuthenticationProvider authenticationProvider) {

getAuthenticationRegistry().authenticationProvider(authenticationProvider);

return this;

}

最后在 HttpSecurity 的 beforeConfigure 方法中完成构建:

@Override

protected void beforeConfigure() throws Exception {

setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

Spring全套教学资料

Spring是Java程序员的《葵花宝典》,其中提供的各种大招,能简化我们的开发,大大提升开发效率!目前99%的公司使用了Spring,大家可以去各大招聘网站看一下,Spring算是必备技能,所以一定要掌握。

目录:

部分内容:

Spring源码

  • 第一部分 Spring 概述
  • 第二部分 核心思想
  • 第三部分 手写实现 IoC 和 AOP(自定义Spring框架)
  • 第四部分 Spring IOC 高级应用
    基础特性
    高级特性
  • 第五部分 Spring IOC源码深度剖析
    设计优雅
    设计模式
    注意:原则、方法和技巧
  • 第六部分 Spring AOP 应用
    声明事务控制
  • 第七部分 Spring AOP源码深度剖析
    必要的笔记、必要的图、通俗易懂的语言化解知识难点

脚手框架:SpringBoot技术

它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。

Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。

  • SpringBoot入门
  • 配置文件
  • 日志
  • Web开发
  • Docker
  • SpringBoot与数据访问
  • 启动配置原理
  • 自定义starter

微服务架构:Spring Cloud Alibaba

同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • 微服务架构介绍
  • Spring Cloud Alibaba介绍
  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

Spring MVC

目录:

部分内容:

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
ibaba介绍

  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

[外链图片转存中…(img-uKVa0T9o-1713748231144)]

[外链图片转存中…(img-sP0EFo1S-1713748231144)]

Spring MVC

目录:

[外链图片转存中…(img-b68u50ie-1713748231144)]

[外链图片转存中…(img-vnAHf8Mv-1713748231144)]

[外链图片转存中…(img-iEpi1Eal-1713748231145)]

部分内容:

[外链图片转存中…(img-8pVjall9-1713748231145)]

[外链图片转存中…(img-kLzGRkJT-1713748231145)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值