Spring Security登录流程分析

news2025/7/25 8:56:37

本文内容来自王松老师的《深入浅出Spring Security》,自己在学习的时候为了加深理解顺手抄录的,有时候还会写一些自己的想法

登录流程分析

        要搞请求Spring Security认证流程,我们先得认识与之相关的三个基本组件:AuthenticationManager、ProviderManager、AuthenticationProvider,同时还要去了解接入认证器的过滤器AbstractAuthenticationProcessingFilter,这四个类搞清楚了,基本上认证流程也清楚了。

AuthenticationManager

        从名称上看AuthenticationManager是一个认证管理器,它定义了Spring Security过滤器要如何执行认证操作。AuthenticationManager在认证成功后会返回一个Authentication对象,这个Authenticaiton会被设置到SecurityContextHolder中。

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

        从AuthenticationManager的源码中可以看到AuthenticationManager接受一个Authentication对象进行身份认证,此时传入的Authentication对象只包含简单的用户名和密码等简单的属性,如果认证成功之后返回的Authentication的属性会得到填充,包含用户具备的角色信息。

        AuthenticationManager是一个接口,它有诸多的实现类,开发者可以自定义AuthenticationManager的实现类。不过在实际开发中,我们使用的最多的是providerManager。在Spring Security框架中默认也是使用ProviderManager。

AuthenticationProvider

        Spring Security支持多种不同的认证方式,不同的认证方式对应不同的身份类型,AuthenticationProvider是针对不同的身份类型执行具体的身份认证。例如常见的DaoAuthenticationProvider用来支持用户名/密码登录认证。其源码如下:

public interface AuthenticationProvider {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
	boolean supports(Class<?> authentication);
}
  • authenticate方法用来执行具体的认证方式

  • supports方法用来判断当前的AuthenticationProvider是否支持对应的身份认证

ProviderManager

        ProviderManager是AuthenticationManager的一个重要实现类,在前面学习的章节中有提到。下面这幅图可以清楚的反应ProviderManager和AuthenticationProvider的关系:

 

        在Spring Security中,由于系统中可能会同时支持多种不同的认证方式,例如:用户名/密码、验证码、手机号码动态验证。而不同的认证方式对应了不同的AuthenticaitonProvider,所以一个完整的认证流程可能由多个AuthenticationProvider来提供。

 

        多个AuthenticationProvider将组成一个列表,这个列表将由ProviderManager来代理。换句话说,在ProviderManager中遍历列表中的每一个AuthenticationProvider去身份认证,最终得到一个认证结果。

        我们重点看一下ProviderManager的authenticate方法:

@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			try {
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
			// If the parent AuthenticationManager was attempted and successful then it
			// will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
			// AuthenticationManager already published it
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}

			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		// If the parent AuthenticationManager was attempted and failed then it will
		// publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
		// parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}

        这段源码也比较简单,逻辑也比较清晰:

  • 首先获取Authentication对象的类型
  • getProviders方法获取当前ProviderManager代理的所有的AuthenticationProvider的对象,挨个的调用AuthenticationProvider的supports方法判断是否支持当前的Authentication对象,要是不支持就调用下一个AuthenticationProvider,如果支持的话就调用当前的AuthenticationProvider的authenticate方法进行认证
  • 调用AuthenticationProvider对象的authenticate如果认证成功,则返回Authentication对象,同时调用copyDetails方法给Authentication对象的details属性赋值
  • 接下来就是异常处理相关的事情

        小伙伴现在大致已经熟悉Authentication、AuthenticationManager、ProviderManager以及AuthenticationProvider的工作原理了。接下我们学习下这些组件是如何跟登录联系起来的。这里就要设计我们下面学习的一个重要的过滤器   ------>   AbstractAuthenticationProcessingFilter。

AbstractAuthenticationProcessingFilter

        作为Spring Security过滤器中的一环AbstractAuthenticationProcessingFilter可以用来处理任何交给它的身份认证。下图描述了AbstractAuthenticationProcessingFilter的工作原理:

 

                AbstractAuthenticationProcessingFilter作为一个抽象类,如果使用用户名/密码登录,那么它对应的实现类是UsernamePasswordAuthenticationFilter,构造出来Authenticaiton的对象则是UsernamePasswordAuthenticaitonToken。至于AuthenticationManager前面学习过,一般情况下她的实现类是ProviderManager,这里在ProviderManager中认证。认证成功会进入成功的回调,否则进入失败的回调。因此对于上面的流程可以再详细一点:

         所以认证的基本流程是这样:

  • 当用户提交登录请求时,UsernamePasswordAuthenticationFilter会从当前请求的HttpServletRequest中提取出用户名、密码,然后创建一个UsernamePasswrodToken对象
  • UsernamePasswordAuthenticationToken对象作为一个Authenticaiton对象传入ProvoderManager中进行具体的认证
  • 如果认证失败,则会进行登录信息存储、Session处理、登录成功事件发布以及登录成功方法回调等操作
  • 如果登录失败,则SecurityContextHolder中相关信息将被清除,登录失败回调也会被调用

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

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

相关文章

Redis——》事务

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 Redis——》事务一、概念二、示例multi、execdiscardwatch三、事务发生错误1、入队阶段发生错误2、执…

数据库课程设计——学籍管理系统

目录 1 问题的提出 1 2 需求分析 2 2.1需求描述 2 2.2数据字典 3 2.2.1数据项 3 2.2.2主要的数据流定义 6 2.3数据流图和业务流图 7 2.3.1顶层数据流图 7 2.3.2第一层数据流图 8 2.3.3第一层数据流图 8 2.3.4第一层数据流图 9 2.3.5第一层数据流图 9 3 概念结构设计 10 3.1实…

docker基础命令

docker基础 docker中的三个基本概念: 镜像: Docker 镜像&#xff08;Image&#xff09;&#xff0c;就相当于是一个 root 文件系统 容器: 镜像&#xff08;Image&#xff09;和容器&#xff08;Container&#xff09;的关系&#xff0c;就像是面向对象程序设计中的类和实例一…

基于STM32的DS18B20多点测温系统(Proteus仿真+程序)

编号&#xff1a;22 基于STM32的DS18B20多点测温系统 功能描述&#xff1a; 本设计由STM32F103单片机三路DS18B20温度传感器1602液晶显示模块组成。 1、主控制器是STM32F103单片机 2、三路共用“单总线”DS1820温度传感器测量温度 3、1602液晶显示温度&#xff0c;保留一位小…

[附源码]java毕业设计明光中学考试系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Meta开源新工具啊,Git地位危险了?

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store 从事编程开发的同学&#xff0c;绝大多数都会和版本控制系统打交道。 提到版本控制系统&#xff0c;目前比较主流的就是Git和SVN&#xff0c;尤其是Git&#xff0c;使用最为广泛。 关于Git和SVN之间…

python绘制Bubble气泡图pltscatter

python绘制Bubble气泡图pltscatter 先上结果&#xff1a; 基础语法&#xff1a; Axes.scatter(x, y**,** sNone**,** cNone**,** markerNone**,** cmapNone**,** normNone**,** vminNone**,** vmaxNone**,** alphaNone**,** linewidthsNone**,** , edgecolorsNone,* plotnonf…

nVisual 场景搭建所需接口

使用nVisua在创建新的项目步骤是搭建场景、创建对象、创建对象连接&#xff0c;本章小编带大家先了解搭建场景需要的接口。 场景搭建可根据自身项目需要搭建园区、建筑、楼层、机房这几类场景。分别用到了地图场景创建接口、CAD场景创建接口、静态图片背景创建接口。 1.地图场…

企业内训系统源码,为企业量身定制学习平台

如何进行企业内训系统开发&#xff1f;不同的直播平台的功能是不同的&#xff0c;企业的发展与员工的素质、能力、工作人效等不可分割&#xff0c;如何提高员工的工作能力&#xff0c;企业内部培训是离不开的&#xff0c;那么企业内训系统如何开发&#xff1f;怎么做一个企业学…

Spring Cloud面试题

什么是Spring Cloud Spring Cloud是目前最常用的微服务开发框架&#xff08;微服务的特点就是"模块化、功能化"&#xff0c;微服务架构的本质是将原来的整体项目划分成多个功能模块&#xff0c;每个功能模块都可以独立运行提供服务&#xff09;&#xff0c;它利用Sp…

wps和office可以同时装吗?

wps和office是很多用户都在使用的办公软件&#xff0c;那就有小白用户问了一台电脑可以存在wps和office吗&#xff1f;两个软件兼容吗&#xff1f;wps和office性质上都是办公软件&#xff0c;但是并不算重复&#xff0c;因此是可以同时安装的。 wps和office能同时安装吗 答&…

合并多个PDF怎么合并?建议学会这几个合并方法

你们平时工作的时候&#xff0c;看到自己的电脑桌面有很多文档文件&#xff0c;会不会觉得很杂乱&#xff1f;如果不将这些资料好好整理一番&#xff0c;都不能好好完成接下来的工作。其实如果是同种类型的PDF文件&#xff0c;我们可以将它们合并&#xff0c;这样既可以归类&am…

STM32单片机DS18B20测温液晶1602显示例程(Proteus仿真+程序)

编号&#xff1a;21 STM32单片机DS18B20测温液晶1602显示例程 功能描述&#xff1a; 本设计由STM32F103C8T6单片机最小系统DS18B20温度传感器1602液晶显示模块组成。 1、主控制器是STM32F103C8T6单片机 2、DS1820温度传感器测量温度 3、1602液晶显示温度&#xff0c;保留一位…

理解Linux32位机器下虚拟地址到物理地址的转化

文章目录前言一、基本概念介绍二、虚拟地址到物理地址的转化过程总结前言 简要介绍LINUX32位系统下虚拟地址到物理地址的转化过程。 一、基本概念介绍 在32位机器下&#xff0c;IO的基本单位是块&#xff08;块&#xff1a;4kb),在程序编译成可执行程序时也划分好了以4kb为单…

Linux的前世今生

14天学习训练营导师课程&#xff1a; 互联网老辛《 符合学习规律的超详细linux实战快速入门》 努力是为了不平庸~ 学习有些时候是枯燥的&#xff0c;但收获的快乐是加倍的&#xff0c;欢迎记录下你的那些努力时刻&#xff08;学习知识点/题解/项目实操/遇到的bug/等等&#xf…

使用STM32CubeMX实现按下按键,电平反转

需提前学习&#xff1a;使用STM32CubeMX实现LED闪烁 目录 原理图分析 按键部分原理图分析 LED部分原理图分析 STM32CubeMX配置 关于STM32CubeMXSYS的Debug忘记配置Serial Wire处理办法 GPIO配置 LED的GPIO配置 KEY1配置 关于PA0后面这个WKUP是什么&#xff1f; 那么啥…

Linux开发工具(4)——Makefile

文章目录Makefilemakefile语法makefile原理Linux小程序倒计时小程序进度条程序Makefile Makefile是Linux下的项目自动化构建工具。 Makefile包含两部分&#xff0c;make是一个指令&#xff0c;makefile是一个文件。 在makefile这个文件里面需要写两部分内容&#xff1a; 依赖…

【LeetCode】891.子序列宽度之和

**> ## 题目描述 一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。 给你一个整数数组 nums &#xff0c;返回 nums 的所有非空 子序列 的 宽度之和 。由于答案可能非常大&#xff0c;请返回对 109 7 取余 后的结果。 子序列 定义为从一个数组里删除一些&#xff…

Scala009--Scala中的数据结构【映射】

目录 一&#xff0c;概述 二&#xff0c;map的声明 1&#xff0c;不可变map 三&#xff0c;HashMap的声明 1&#xff0c;可变hashmap 四&#xff0c;map常用函数 1&#xff0c;查看map中的元素个数 size 2&#xff0c;获取map集合中key对应的value值 1&#xff09;使…

react源码中的hooks

今天&#xff0c;让我们一起深入探究 React Hook 的实现方法&#xff0c;以便更好的理解它。但是&#xff0c;它的各种神奇特性的不足是&#xff0c;一旦出现问题&#xff0c;调试非常困难&#xff0c;这是由于它的背后是由复杂的堆栈追踪&#xff08;stack trace&#xff09;支…