Spring Security 6.x 系列(9)—— 基于过滤器链的源码分析(二)

一、前言

在本系列文章:

Spring Security 6.x 系列(4)—— 基于过滤器链的源码分析(一)中着重分析了Spring SecuritySpring Boot 的自动配置、 DefaultSecurityFilterChain 的构造流程、FilterChainProxy 的构造流程。

Spring Security 6.x 系列(7)—— 源码分析之Builder设计模式中详细分析了Spring SecurityBuilder设计模式和WebSecurityHttpSecurityAuthenticationManagerBuilder 这三个重要构造者公共的部分。

Spring Security 6.x 系列(8)—— 源码分析之配置器SecurityConfigurer接口及其分支实现中分析SecurityConfigurer接口及其分支实现。

今天沿着Spring Boot自动配置中对未被介绍的@EnableGlobalAuthentication进行分析展开。

二、AuthenticationConfiguration

@EnableWebSecurity注解中引入了@EnableGlobalAuthentication注解:

在这里插入图片描述
@EnableGlobalAuthentication注解上使用@Import(AuthenticationConfiguration.class)注解引入本类:

在这里插入图片描述
我们先看看里面有些什么?

在这里插入图片描述

2.1 成员变量

// 状态标志位,AuthenticationManager是否正处于构建过程中
private AtomicBoolean buildingAuthenticationManager = new AtomicBoolean();
// Application容器
private ApplicationContext applicationContext;
// 用于记录所要构建的AuthenticationManager 
private AuthenticationManager authenticationManager;
// AuthenticationManager是否已经被构建的标志
private boolean authenticationManagerInitialized;
// 全局认证配置适配器列表
private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections.emptyList();
// 对象后处理器
private ObjectPostProcessor<Object> objectPostProcessor;

2.2 核心内容

2.2.1 authenticationManagerBuilder 方法

实例化一个AuthenticationManagerBuilder类型的构造者,用于构造AuthenticationManager实例:

@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
		ApplicationContext context) {
	/**
	 * Lazy密码加密器:该对象创建时容器中可能还不存在真正的密码加密器
	 * 但是用该lazy密码加密器进行加密或者密码匹配时,会从容器中获取类型为PasswordEncoder的密码加密器,
	 * 如果容器中不存在类型为PasswordEncoder的密码加密器,则使用
	 * PasswordEncoderFactories.createDelegatingPasswordEncoder()创建一个PasswordEncoder供随后加密或者密码匹配使用
	 * LazyPasswordEncoder是定义在当前配置类中的一个内部类
	 */
	LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
	/**
	 *获取认证事件的发布器
	 */
	AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
	/**
	 * 生成AuthenticationManagerBuilder实例,使用实现类为DefaultPasswordEncoderAuthenticationManagerBuilder
	 * DefaultPasswordEncoderAuthenticationManagerBuilder是定义在该配置类中的一个内部类,它继承自AuthenticationManagerBuilder
	 * 是SpringSecurity缺省使用的 AuthenticationManagerBuilder实现类
	 */
	DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
			objectPostProcessor, defaultPasswordEncoder);
	/**
	 *如果有事件发布器,则设置
	 */
	if (authenticationEventPublisher != null) {
		result.authenticationEventPublisher(authenticationEventPublisher);
	}
	return result;
}

private AuthenticationEventPublisher getAuthenticationEventPublisher(ApplicationContext context) {
	if (context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
		return context.getBean(AuthenticationEventPublisher.class);
	}
	return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
}

关于AuthenticationManagerBuilder在下文会详细介绍。

2.2.2 getAuthenticationManager 方法

根据配置生成认证管理器 AuthenticationManager,该方法具有幂等性且进行了同步处理 。

首次调用会触发真正的构建过程生成认证管理器 AuthenticationManager,再次的调用都会返回首次构建的认证管理器 AuthenticationManager

public AuthenticationManager getAuthenticationManager() throws Exception {
	// authenticationManager如果已经被构建则直接返回authenticationManager
	if (this.authenticationManagerInitialized) {
		return this.authenticationManager;
	}
	// 获取容器中的AuthenticationManagerBuilder实例用于创建AuthenticationManager
	AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
	// 如果已经正在使用authBuilder进行构建, 则这里直接返回一个包装了构建器authBuilder的AuthenticationManagerDelegator对象
	// true表示现在正在构建过程中,false表示现在不在构建过程中
	if (this.buildingAuthenticationManager.getAndSet(true)) {
		return new AuthenticationManagerDelegator(authBuilder);
	}
	// 将全局配置设置到AuthenticationManagerBuilder中
	for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
		authBuilder.apply(config);
	}
	// 构建AuthenticationManager
	this.authenticationManager = authBuilder.build();
	 
	 /**
     * 	如果容器中没有用于构建 AuthenticationManager的 AuthenticationProvider bean供authBuilder使用,也没有为 authBuilder 置 parent AuthenticationManager时,
     *  则上面声明的 authenticationManager为 null。不过这种情况缺省情况下并不会发生:
     *  因为该配置类中 bean InitializeUserDetailsBeanManagerConfigurer为 authBuilder添加的 InitializeUserDetailsBeanManagerConfigurer 
     *  会在这种情况下构造一个 DaoAuthenticationProvider对象给 authBuilder使用。
     *  另外,一般情况下,开发人员也会提供自己的 AuthenticationProvider 实现类。
     *  通常经过上面的 authBuilder.build(),authenticationManager 对象都会被创建,
     *  但是如果 authenticationManager 未被创建,这里尝试使用 getAuthenticationManagerBean()再次设置 authenticationManage
     */
	if (this.authenticationManager == null) {
		this.authenticationManager = getAuthenticationManagerBean();
	}
	//将authenticationManagerInitialized 设置为true,说明authenticationManager已经初始化完成
	this.authenticationManagerInitialized = true;
	// 返回构建好的AuthenticationManager
	return this.authenticationManager;
}

2.2.3 初始化GlobalAuthenticationConfigurerAdapter的三个实现

定义一个EnableGlobalAuthenticationAutowiredConfigurer,他会加载使用了注解@EnableGlobalAuthenticationBean,用于配置全局AuthenticationManagerBuilder

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

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 = this.context
				.getBeansWithAnnotation(EnableGlobalAuthentication.class);
		if (logger.isTraceEnabled()) {
			logger.trace(LogMessage.format("Eagerly initializing %s", beansWithAnnotation));
		}
	}

}

定义一个InitializeUserDetailsBeanManagerConfigurer配置类,用于配置单例的UserDetailsService时延时配置全局AuthenticationManagerBuilder

@Bean
public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(
		ApplicationContext context) {
    // InitializeUserDetailsBeanManagerConfigurer是GlobalAuthenticationConfigurerAdapter的一个实现
	return new InitializeUserDetailsBeanManagerConfigurer(context);
}

定义一个InitializeAuthenticationProviderBeanManagerConfigurer配置类,用于配置单例的UserDetailsService时延时加载全局AuthenticationProvider

@Bean
public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(
		ApplicationContext context) {
	// InitializeAuthenticationProviderBeanManagerConfigurer是GlobalAuthenticationConfigurerAdapter的一个实现
	return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
}

2.2.4 AuthenticationManagerDelegator

AuthenticationManagerDelegatorAuthenticationManager的一个包装类或是委托类,主要是为了防止在初始化AuthenticationManager时发生无限递归:

  • 当这个内部类被构建时,会注入一个AuthenticationManagerBuilder实例。
  • authenticate()方法具有幂等性且进行了同步处理
    • 当这个类的authenticate()方法被第一次调用时会使用AuthenticationManagerBuilder创建一个AuthenticationManager保存到这个类的delegate属性中,同时将delegateBuilder置空,然后将实际鉴权处理交给AuthenticationManager
    • 后续再调用authenticate()方法就只是使用已经创建好的AuthenticationManager实例。
static final class AuthenticationManagerDelegator implements AuthenticationManager {

	private AuthenticationManagerBuilder delegateBuilder;

	private AuthenticationManager delegate;

	private final Object delegateMonitor = new Object();
    // 初始化一个AuthenticationManagerBuilder实例
	AuthenticationManagerDelegator(AuthenticationManagerBuilder delegateBuilder) {
		Assert.notNull(delegateBuilder, "delegateBuilder cannot be null");
		this.delegateBuilder = delegateBuilder;
	}
    // 具有幂等性且进行了同步处理
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
	    // 如果已经包含创建成功的AuthenticationManager,直接调用AuthenticationManager.authenticate()方法返回一个Authentication
		if (this.delegate != null) {
			return this.delegate.authenticate(authentication);
		}
		// 如果没有包含创建成功的AuthenticationManager,进入同步方法
		synchronized (this.delegateMonitor) {
			if (this.delegate == null) {
	    // 使用AuthenticationManagerBuilder构建一个AuthenticationManager,
	    // 将值设置到AuthenticationManagerDelegator的delegate属性
				this.delegate = this.delegateBuilder.getObject();
				this.delegateBuilder = null;
			}
		}
		// 调用AuthenticationManager.authenticate()方法返回一个Authentication
		return this.delegate.authenticate(authentication);
	}

	@Override
	public String toString() {
		return "AuthenticationManagerDelegator [delegate=" + this.delegate + "]";
	}
}

三、AuthenticationManagerBuilder

3.1 继承关系

在这里插入图片描述
在前面文章了解过 WebSecurityHttpSecurityAuthenticationManagerBuilder 这三个重要构造者有一条继承树:

|- SecurityBuilder
	|- AbstractSecurityBuilder
		|- AbstractConfiguredSecurityBuilder

3.2 ProviderManagerBuilder

ProviderManagerBuilder是一个接口,继承 SecurityBuilder ,也是一个构造器,它构造的对象是 AuthenticationManager(认证管理器 ):

源码注释:
用于执行创建 ProviderManagerSecurityBuilder(构造器) 接口

/**
 * Interface for operating on a SecurityBuilder that creates a {@link ProviderManager}
 *
 * @param <B> the type of the {@link SecurityBuilder}
 * @author Rob Winch
 */
public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>>
		extends SecurityBuilder<AuthenticationManager> {

	/**
	 * Add authentication based upon the custom {@link AuthenticationProvider} that is
	 * passed in. Since the {@link AuthenticationProvider} implementation is unknown, all
	 * customizations must be done externally and the {@link ProviderManagerBuilder} is
	 * returned immediately.
	 *
	 * Note that an Exception is thrown if an error occurs when adding the
	 * {@link AuthenticationProvider}.
	 * @return a {@link ProviderManagerBuilder} to allow further authentication to be
	 * provided to the {@link ProviderManagerBuilder}
	 */
	B authenticationProvider(AuthenticationProvider authenticationProvider);

}

它只有两个抽象方法:

  • build() 方法继承自父类,用来构建AuthenticationManager对象

  • authenticationProvider(AuthenticationProvider authenticationProvider) 方法

    这个方法由子类实现,源码对这个方法的描述:

    • 根据传入的自定义 AuthenticationProvider添加身份验证(authentication )。
    • 由于 AuthenticationProvider 实现未知,因此所有自定义都必须在外部完成并立即返回
      ProviderManagerBuilder
    • 添加过程中出错会抛异常。

个人理解:
这个接口对父接口扩展的主要点在于以authenticationProvider(AuthenticationProvider authenticationProvider) 方法的形式向构造器添加自定义的认证提供者(AuthenticationProvider),每次添加完之后会立即返回添加完认证提供者之后的构造器,这样就可以利用这个返回对象继续添加向其认证提供者(AuthenticationProvider)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/217946.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

12.4 C++ 作业

完成沙发床的多继承 #include <iostream>using namespace std;//封装 沙发 类 class Sofa { private:string *sitting; public://无参构造函数Sofa(){cout << "Sofa::无参构造函数" << endl;}//有参构造函数Sofa(string s):sitting(new string(s)…

手机升级到iOS15.8后无法在xcode(14.2)上真机调试

之前手机是iOS14.2的系统,在xcode上进行真机测试运行良好&#xff0c;因为想要使用Xcode的Instruments功能&#xff0c;今天将系统更新到了iOS15.8 &#xff0c;结果崩了 说是Xcode和手机系统不兼容不能进行真机测试。在网上查不好些方法&#xff0c;靠谱的就是下载相关版本的…

C语言小游戏:三子棋

目录 &#x1f30d;前言 &#x1f685;目录设计 &#x1f48e;游戏逻辑设置 ⚔三子棋棋盘设计 ⚔三子棋运行逻辑 &#x1f440;怎么设置人下棋 &#x1f440;怎么设置电脑下棋 ✈如何判断输赢 ✍结语 &#x1f30d;前言 Hello,csdn的各位小伙伴你们好啊!这次小赵给大…

ArcGIS平滑处理栅格数据

一、实验背景 基于栅格数据的空间分析&#xff0c;常常需要根据特定的分析场景对栅格数据进行处理&#xff0c;如栅格数据的噪声处理。噪声是属性值具有突跃特征的像元位置&#xff0c;直接对带有噪声的栅格数据进行分析会对结果造成较大的影响。而降噪的主要方法之一是平滑&a…

12.4_黑马MybatisPlus笔记(下)

目录 11 12 thinking&#xff1a;关于Mybatis Plus中BaseMapper和IService&#xff1f; 13 ​编辑 thinking&#xff1a;CollUtil.isNotEmpty? 14 thinking&#xff1a;Collection、Collections、Collector、Collectors&#xff1f; thinking&#xff1a;groupBy&#…

风格迁移网络修改流程(自用版)

一. AdaAttN-Revisit Attention Mechanism in Arbitrary Neural Style Transfer&#xff08;ICCV2021&#xff09; 下载vgg_normalised.pth打开visdom python -m visdom.server在 train_adaattn.sh 中配置 content_path、style_path 和 image_encoder_path&#xff0c;分别表…

FFmpeg在Centos服务器上离线安装(包含所需依赖)并实现拉取rtsp流与推送至rtmp服务器

场景 Windows上使用FFmpeg实现rtsp视频流推流到RTMP流媒体服务器(EasyCVR流媒体服务器)&#xff1a; Windows上使用FFmpeg实现rtsp视频流推流到RTMP流媒体服务器(EasyCVR流媒体服务器)_rtsp 转流-CSDN博客 上面讲了在windows上ffmpeg的应用示例&#xff0c;如果是在centos服…

Hadoop进阶学习---HDFS分布式文件存储系统

1.hdfs分布式文件存储的特点 分布式存储:一次写入,多次读取 HDFS文件系统可存储超大文件,时效性较差. HDFS基友硬件故障检测和自动快速恢复功能. HDFS为数据存储提供很强的扩展能力. HDFS存储一般为一次写入,多次读取,只支持追加写入,不支持随机修改. HDFS可以在普通廉价的机器…

canvas绘制小丑

说明&#xff1a; 借鉴博主基于canvas绘制一个爱心(10行代码就够了) - 掘金 (juejin.cn) 代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content&quo…

震坤行2023安全月活动顺利收官

震坤行2023安全月活动顺利收官 2023年6月&#xff0c;是第22个全国“安全生产月”&#xff0c;主题为 “人人讲安全、个个会应急”。震坤行工业超市(上海)有限公司基于国家 “安全生产月”的主题要求&#xff0c;以及公司具体的安全形势&#xff0c;于6月1日在全公司范围内正式…

EM32DX-E4【C#】

1外观&#xff1a; ecat总线&#xff0c;分布式io 2电源&#xff1a; 靠近SW拨码&#xff1a;24V 中间&#xff1a;0V 靠近面板&#xff1a;PE接地 3DI&#xff1a; 6000H DI输入寄存器 16-bit &#xff08;16位输入&#xff09; 00H U8 子索引总数 01H Unsigned16 IN1…

TCP 半连接队列和全连接队列

在 TCP 三次握手的时候&#xff0c;Linux 内核会维护两个队列&#xff0c;分别是&#xff1a; 半连接队列&#xff0c;也称 SYN 队列&#xff1b; 全连接队列&#xff0c;也称 accept 队列&#xff1b; 服务端收到客户端发起的 SYN 请求后&#xff0c;内核会把该连接存储到半连…

数据库系统概论期末经典大题讲解(用关系代数进行查询)

今天也是结束的最为密集的考试周&#xff0c;在分析过程中自己也有些许解题的感悟&#xff0c;在此分享出来&#xff0c;希望能帮到大家期末取得好成绩。 一.专门的关系运算 1.选择&#xff08;σ&#xff09; 选择操作符用于从关系中选择满足特定条件的元组 例如&#xff0c;…

计算机组成原理学习-总线总结

复习本章时&#xff0c;思考以下问题&#xff1a; 1)引入总线结构有什么好处&#xff1f;2)引入总线结构会导致什么问题&#xff1f;如何解决&#xff1f;

【专题】【中值定理-还原大法】

1&#xff09;构造辅助函数 2&#xff09;罗尔定理&#xff1a; 闭区间连续&#xff0c;开区间可导 F&#xff08;a&#xff09;F&#xff08;b&#xff09; 3&#xff09;F‘&#xff08;ξ&#xff09;0&#xff0c;原命题得证 极限保号性&#xff1a;

WPS论文写作——公式和公式序号格式化

首先新建一个表格&#xff0c;表格尺寸按你的需求来确定&#xff0c;直接 插入--》表格 即可。 然后在表格对应位置填上公式&#xff08;公式要用公式编辑器&#xff09;和公式序号&#xff0c;然后可以按照单独的单元格或者整行或整列等来设置样式&#xff0c;比如居中对齐、…

微服务实战系列之Cache(技巧篇)

前言 凡工具必带使用说明书&#xff0c;如不合理的使用&#xff0c;可能得到“意外收获”。这就好比每个人擅长的领域有所差异&#xff0c;如果放错了位置或用错了人&#xff0c;也一定会让 Leader 们陷入两难之地&#xff1a;“上无法肩负领导之重托&#xff0c;下难免失去伙伴…

123456前端调AES加密方法变为YehdBPev

使用密码加密服务: pig4cloud 加密服务

(华为)网络工程师教程笔记(网工教程)网工入门——3、静态路由路由表的配置

参考文章&#xff1a;【全236集】网络工程师从基础入门到进阶必学教程&#xff01;通俗易懂&#xff0c;2023最新版&#xff0c;学完即可就业&#xff01;网工入门_华为认证_HCIA_HCIP_数据通信_网工学习路线 文章目录 13. 网工入门10-静态路由&#xff08;路由表的配置&#x…

传输层可靠传输的原理

目录 1.停止等待协议 2.连续ARQ协议 3.TCP报文段的首部格式 4.TCP的滑动窗口机制 &#xff08;1&#xff09;发送窗口 &#xff08;2&#xff09;接收窗口 &#xff08;3&#xff09;发送缓存 5.超时重传时间的选择 6.选择确认SACK(Selective ACK) 7.使用滑动窗口实现…