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

news2024/11/15 12:49:08

一、前言

在本系列文章:

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)。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入学习锁--Synchronized各种使用方法

一、什么是synchronized 在Java当中synchronized通常是用来标记一个方法或者代码块。在Java当中被synchronized标记的代码或者方法在同一个时刻只能够有一个线程执行被synchronized修饰的方法或者代码块。因此被synchronized修饰的方法或者代码块不会出现数据竞争的情况&#x…

Spring Boot中使用Swagger

1. 启用Swagger 1.1 启用注解扫描和文档接口 直接在POM文件引入依赖 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version> </dependency>1.2 启动swagger-u…

ARM与大模型,狭路相逢

编辑&#xff1a;阿冒 设计&#xff1a;沐由 从去年底至今&#xff0c;伴随着OpenAI旗下ChatGPT的火爆&#xff0c;一波AI大模型推动着AI应用全面进入了大模型时代。与此同时&#xff0c;随着边缘算力的提升&#xff0c;AI大模型的部署也逐渐从云端涉入到边缘。 世界对AI算力的…

黑马一站制造数仓实战1

1. 项目目标 一站制造 企业中项目开发的落地&#xff1a;代码开发 代码开发&#xff1a;SQL【DSL SQL】 SparkCore SparkSQL 数仓的一些实际应用&#xff1a;分层体系、建模实现 2. 内容目标 项目业务介绍&#xff1a;背景、需求 项目技术架构&#xff1a;选型、架构 项目环境…

Selenium 自动化高级操作与解决疑难杂症,如无法连接、使用代理等

解决 Selenium 自动化中的常见疑难杂症 这里记录一些关于 Selenium的常用操作和疑难杂症。 有一些细节的知识点就不重复介绍了&#xff0c;因为之前的文章中都有&#xff01; 如果对本文中的知识点有疑问的&#xff0c;可以先阅读我以前分享的文章&#xff01; 知识点&…

1-4、调试汇编程序

语雀原文链接 文章目录 1、执行过程第一步&#xff1a;源程序第二步&#xff1a;编译连接第三步&#xff1a;执行 2、DOSBox运行程序第1步 进入EDIT.EXE第2步 编写源程序第3步 编译第4步 连接第5步 执行完整过程 3、DEBUG跟踪执行过程加载程序到内存执行程序debug和源程序数字…

【导航控制器总结-导航控制器栈 Objective-C语言】

一、导航控制器总结 1.我们接着上一堂课的内容继续 我们上节课说到哪里了,是不是就是对这个导航控制器的一个总结啊 然后,使用的注意事项 2.导航控制器使用注意事项: 1)第一点,使用导航控制器,你在创建的时候,需要给它指定一个根控制器 创建导航控制器的同时,指定…

【译】如何在调试时分析CPU和内存(Analyze CPU and Memory while Debugging)

您想了解如何使您的代码运行得更快&#xff0c;使用更少的内存&#xff0c;或者只是找出您的代码是否有CPU或内存问题?你当然会——你是一名开发人员!但是&#xff0c;内存和性能调优经常会遇到“重要但不紧急”的任务&#xff0c;因为真正紧急的事情&#xff0c;您似乎根本无…

优化 SQL 日志记录的方法

为什么 SQL 日志记录是必不可少的 SQL 日志记录在数据库安全和审计中起着至关重要的作用&#xff0c;它涉及跟踪在数据库上执行的所有 SQL 语句&#xff0c;从而实现审计、故障排除和取证分析。SQL 日志记录可以提供有关数据库如何访问和使用的宝贵见解&#xff0c;使其成为确…

Dockerfile脚本编写流程及示例

学习dockerfile指令 Dockerfile 指令 说明 FROM 指定基础镜像 MAINTAINER 声明镜像的维护者 LABEL 添加元数据标签 RUN 在容器中执行命令 CMD 容器启动后默认执行的命令 EXPOSE 暴露容器的端口 ENV 设置环境变量 ADD 将文件、目录或远程文件添加到容器中 COP…

[ 蓝桥杯Web真题 ]-外卖给好评

目录 介绍 准备 目标 效果 规定 思路 解答参考 介绍 外卖是现代生活中必备的一环。收到外卖后&#xff0c;各大平台软件常常会邀请用户在口味&#xff0c;配送速度等多个方面给与评分。在 element-ui 组件中&#xff0c;已经有相应的 Rate 组件&#xff0c;但是已有组件…

论文解读--PointPillars- Fast Encoders for Object Detection from Point Clouds

PointPillars--点云目标检测的快速编码器 摘要 点云中的物体检测是许多机器人应用(如自动驾驶)的重要方面。在本文中&#xff0c;我们考虑将点云编码为适合下游检测流程的格式的问题。最近的文献提出了两种编码器;固定编码器往往很快&#xff0c;但牺牲了准确性&#xff0c;而…

【latex笔记】双栏格式下插入单栏、双栏格式图片

双栏格式下插入单栏、双栏格式图片 1.缘起multicols2.双栏格式 插入单栏图片3.双栏格式 插入双栏图片 1.缘起multicols 插入双栏格式图片问题被困扰了有很长一段时间&#xff0c;查看网络资源也一直没找到解决方法&#xff0c;今天查看Latex官方文档&#xff0c;才发现因为mul…

spring cloud 整合Feign经行远程调用

文章目录 Feign远程调用Feign替代RestTemplate1&#xff09;引入依赖2&#xff09;添加注解3&#xff09;编写Feign的客户端4&#xff09;测试5&#xff09;总结 自定义配置配置文件方式Java代码方式 Feign使用优化 Feign远程调用 先来看我们以前利用RestTemplate发起远程调用…

将rtsp视频流发送到AWS Kinesis Video Streams的方案——使用Gstreamer(C++) Command Line

大纲 1 创建Kinesis Video Streams1.1 创建视频流1.2 记录Creation Time 2 创建策略2.1 赋予权限2.2 限制资源2.3 Json格式描述&#xff08;或上面手工设置&#xff09;2.4 注意事项 3 创建IAM用户3.1 生成密钥对3.2 附加策略3.3 记录访问密钥对 4 编译C 创建者库5 发送6 检查参…

聊聊 Jetpack Compose 的 “状态订阅自动刷新” -- mutableStateListOf

Jekpack Compose “状态订阅&自动刷新” 系列&#xff1a; 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - MutableState/mutableStateOf 】 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - remember 和重组作用域 】 【 聊聊 Jetpack Compose 的 …

非标设计之气缸类型

空压机&#xff1a; 空压机又称空气压缩机&#xff0c;简单来说就是将机械能转化为压力能来进行工作的&#xff0c;空压机在电力行业应用比较多&#xff0c;除了在电力行业应用较多外&#xff0c;其实空压机还有一个比较常见的用途就是用来制冷和分离气体&#xff0c;输送气体…

java SSM毕业生信息管理myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

前言 学校的规模不断扩大&#xff0c;学生数量急剧增加&#xff0c;有关学生的各种信息量也成倍增长。面对庞大的信息量需要有学生信息管理系统来提高学生管理工作的效率。通过这样的系统可以做到信息的规范管理、科学统计和快速查询、修改、增加、删除等&#xff0c;从而减少管…

【力扣热题100】207. 课程表 python 拓扑排序

【力扣热题100】207. 课程表 python 拓扑排序 写在最前面207. 课程表解决方案&#xff1a;判断是否可以完成所有课程的学习方法&#xff1a;拓扑排序实现步骤Python 实现性能分析结论 写在最前面 刷一道力扣热题100吧 难度中等 https://leetcode.cn/problems/course-schedule…

[leetcode ~二叉树] 模版

文章目录 1. 左叶子之和2. 翻转二叉树 E 1. 左叶子之和 :::details 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中&#xff0c;有两个左叶子&#xff0c;分别是 9 和 15&…