SpringSecurity原理解析(二):认证流程
1、SpringSecurity认证流程包含哪几个子流程?
1)账号验证
2)密码验证
3)记住我—>Cookie记录
4)登录成功—>页面跳转
2、UsernamePasswordAuthenticationFilter
在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter这个过滤
器中实现的,UsernamePasswordAuthenticationFilter 继承于
AbstractAuthenticationProcessingFilter 这个父类。
当请求进来时,在doFilter 方法中会对请求进行拦截,判断请求是否需要认证,若不需要
认证,则放行;否则执行认证逻辑;
1

注意:UsernamePasswordAuthenticationFilter 类中是没有 doFilter 方法的,doFilter
方法是继承自父类 UsernamePasswordAuthenticationFilter 的。
doFilter 方法代码如下:
//执行过滤的方法,所有请求都走这个方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {//请求和应答类型转换HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;//判断请求是否需要认证处理,若不需要认证处理,则直接放行if (!this.requiresAuthentication(request, response)) {//放行,往下走chain.doFilter(request, response);} else {//执行到这里,进行认证处理if (this.logger.isDebugEnabled()) {this.logger.debug("Request is to process authentication");}Authentication authResult;try {//处理认证,然后返回 Authentication 认证对象//重点authResult = this.attemptAuthentication(request, response);//认证失败if (authResult == null) {return;}//认证成功之后注册sessionthis.sessionStrategy.onAuthentication(authResult, request, response);} catch (InternalAuthenticationServiceException var8) {this.logger.error("An internal error occurred while trying to authenticate the user.", var8);this.unsuccessfulAuthentication(request, response, var8);return;} catch (AuthenticationException var9) {this.unsuccessfulAuthentication(request, response, var9);return;}//if (this.continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//认证成功后的处理this.successfulAuthentication(request, response, chain, authResult);}}protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {if (this.logger.isDebugEnabled()) {this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);}//将认证成功后的对象保存到 SecurityContext中SecurityContextHolder.getContext().setAuthentication(authResult);//处理 remember-me属性this.rememberMeServices.loginSuccess(request, response, authResult);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}//认证成功后,页面跳转this.successHandler.onAuthenticationSuccess(request, response, authResult);}
上边的核心代码是下边这一行:

attemptAuthentication方法的作用是获取Authentication对象其实就是对应的认证过程,
attemptAuthentication 方法在子类UsernamePasswordAuthenticationFilter 中实现的。
attemptAuthentication 方法代码如下:
//认证逻辑
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {//如果我们设置了该认证请求只能以post方式提交,且当前请求不是post请求,表示当前请求不符合//认证要求,直接抛出异常,认证失败if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());} else {//执行到这里表示开始执行认证逻辑//从请求中获取用户名和密码String username = this.obtainUsername(request);String password = this.obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();//将用户名和密码包装成 UsernamePasswordAuthenticationToken 对象UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);//设置用户提交的信息到 UsernamePasswordAuthenticationToken 中this.setDetails(request, authRequest);//getAuthenticationManager():获取认证管理器//authenticate:真正处理认证的方法return this.getAuthenticationManager().authenticate(authRequest);}}
1、
3、AuthenticationManager
AuthenticationManager接口中就定义了一个方法authenticate方法,用于处理认证的请求;
AuthenticationManager 接口定义如下:
public interface AuthenticationManager {//处理认证请求Authentication authenticate(Authentication authentication) throws AuthenticationException;}
在这里AuthenticationManager的默认实现是ProviderManager.而在ProviderManager的
authenticate方法中实现的操作是循环遍历成员变量List<AuthenticationProvider> providers
。该providers中如果有一个AuthenticationProvider的supports函数返回true,那么就会调
用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个认证过程结束。
如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证
成功则为认证成功。
authenticate 方法定义如下:
//执行认证逻辑
public Authentication authenticate(Authentication authentication)throws AuthenticationException {//获取 Authentication 对象的类型Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;AuthenticationException parentException = null;Authentication result = null;Authentication parentResult = null;boolean debug = logger.isDebugEnabled();//getProviders():获取系统支持的各种认证方式,如:QQ、微信、微博等等for (AuthenticationProvider provider : getProviders()) {//判断当前的 provider认证处理器 是否支持当前请求的认证类型,若不支持,则跳过if (!provider.supports(toTest)) {continue;}if (debug) {logger.debug("Authentication attempt using "+ provider.getClass().getName());}//执行到这里说明当前认证处理器支持当前请求的认证,try {//执行认证操作result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException e) {//。。。。。省略 。。。。。}catch (InternalAuthenticationServiceException e) {//。。。。。省略 。。。。。}catch (AuthenticationException e) {//。。。。。省略 。。。。。}}//如果循环结束后还没找到支持当前请求的认证处理器provider ,且父类不为空,则//尝试调用父类的认证方法进行认证处理if (result == null && parent != null) {// Allow the parent to try.try {result = parentResult = parent.authenticate(authentication);}catch (ProviderNotFoundException e) {//。。。。。省略 。。。。。}catch (AuthenticationException e) {//。。。。。省略 。。。。。}}//清空密码凭证if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {// Authentication is complete. Remove credentials and other secret data// from authentication((CredentialsContainer) result).eraseCredentials();}//if (parentResult == null) {eventPublisher.publishAuthenticationSuccess(result);}return result;}// //异常处理if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}//if (parentException == null) {prepareException(lastException, authentication);}throw lastException;}
在上边的代码中,我们重点看的是下边这一行:
result = provider.authenticate(authentication);
因为是用户认证,所以这里authenticate方法走是AbstractUserDetailsAuthenticationProvider
类中的实现,
AbstractUserDetailsAuthenticationProvider.authenticate 方法定义如下所示:
//认证操作
public Authentication authenticate(Authentication authentication)throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));// 获取提交的账号String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();//标记,是否使用缓存,默认是使用的,先从缓存中查找提交的账号//若账号已经登录,则缓存中应该boolean cacheWasUsed = true;//根据账号名称从缓存中查找账号,若缓存中不存在该账号,则需要认证UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {//若缓存中不存在该账号,没有缓存cacheWasUsed = false;try {//账号认证user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);}catch (UsernameNotFoundException notFound) {//。。。。。省略 。。。。。}Assert.notNull(user,"retrieveUser returned null - a violation of the interface contract");}try {//如果账号存在,即账号认证成功,则这里就开始密码认证//密码校验前的前置检查,检查账号是否过期、是否锁定等preAuthenticationChecks.check(user);//密码校验//user: 数据库中的数据//authentication: 表单提交的数据additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}catch (AuthenticationException exception) {//。。。。。省略 。。。。。}//检查凭证是否过期postAuthenticationChecks.check(user);//将用户保存到缓存中if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();}return createSuccessAuthentication(principalToReturn, authentication, user);
}//创建具体的 Authentication 对象
protected Authentication createSuccessAuthentication(Object principal,Authentication authentication, UserDetails user) {// user.getAuthorities():返回用户的权限UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(),authoritiesMapper.mapAuthorities(user.getAuthorities()));result.setDetails(authentication.getDetails());return result;}
密码前置校验如下图所示:

然后进入到retrieveUser方法中,retrieveUser和additionalAuthenticationChecks 方法
具体的实现是DaoAuthenticationProvider 类中实现的,如下所示
@Overrideprotected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {// getUserDetailsService会获取到我们自定义的UserServiceImpl对象,也就是会走我们自定义的认证方法了UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {//。。。。。省略 。。。。。}catch (InternalAuthenticationServiceException ex) {//。。。。。省略 。。。。。}catch (Exception ex) {//。。。。。省略 。。。。。}}//具体的密码校验逻辑
protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {//凭证为空(即密码没传进来),则直接抛出异常if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}//获取表单提交的密码String presentedPassword = authentication.getCredentials().toString();//拿表单提交的密码,与数据库中的密码进行匹配,若匹配失败,则抛出异常if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}}
相关文章:
SpringSecurity原理解析(二):认证流程
1、SpringSecurity认证流程包含哪几个子流程? 1)账号验证 2)密码验证 3)记住我—>Cookie记录 4)登录成功—>页面跳转 2、UsernamePasswordAuthenticationFilter 在SpringSecurity中处理认证逻辑是在UsernamePas…...
数据中台 | 数据资源管理平台介绍
01 产品概述 数据资源的盘查、集成、存储、组织、共享等全方位管理能力,无论对于企业的数字化转型,还是对企业数据资产的开发、运营、交易及入表,都具有极为关键的作用。今天,小兵就来为大家介绍我们自研数据智能平台中的核心产品…...
智慧环保平台建设方案
智慧环保平台建设方案摘要 政策导向与建设背景 背景:全国生态环境保护大会提出坚决打好污染防治攻坚战,推动生态文明建设,目标是在2035年实现生态环境质量根本好转。构建生态文明体系,包括生态文化、生态经济、目标责任、生态文明…...
SpringMVC映射请求;SpringMVC返回值类型;SpringMVC参数绑定;
一,SpringMVC映射请求 SpringMVC 使用 RequestMapping 注解为控制器指定可以处理哪些URL请求 1.1RequestMapping修饰类 注解RequestMapping修饰类,提供初步的请求映射信息,相对于WEB应用的跟目录。 注: 如果在类名前࿰…...
【第28章】Spring Cloud之Sentinel注解支持
文章目录 前言一、注解埋点支持二、SentinelResource 注解三、实战1. 准备2. 纯资源定义3. 添加资源配置 四、熔断(fallback)1. 业务代码1.1 Controller1.2 Service1.3 ServiceImpl 2. 熔断配置3. 熔断测试 总结 前言 上一章我们已经完成了对Sentinel的适配工作,这…...
鼎捷新一代PLM 荣膺维科杯 “2023年度行业优秀产品奖”
近日,由中国高科技行业门户OFweek维科网主办的“全数会2024(第五届)中国智能制造数字化转型大会暨维科杯工业自动化及数字化行业年度评选颁奖典礼”在深圳隆重举办。这不仅是中国工业自动化及数字化行业的一大品牌盛会,亦是高科技…...
如何升级用 Helm 安装的极狐GitLab Runner?
本分分享如何对 Helm 安装的 Runner 进行升级。整个过程分为三步:1、确定 Runner 最新版本或者想要升级的版本是否存在;2、用 Helm upgrade 命令进行升级;3、升级确认。 极狐GitLab 为 GitLab 的中国发行版,中文版本对中国用户更…...
08 vue3之认识bem架构及less sass 和scoped
bem架构 他是一种css架构 oocss 实现的一种 (面向对象css) ,BEM实际上是block、element、modifier的缩写,分别为块层、元素层、修饰符层,element UI 也使用的是这种架构 1. BEM架构 1. 介绍 1. BEM是Block Element M…...
静态库的制作
静态库是一组对象文件的集合,它们在编译时被链接到可执行文件中。这意味着,静态库中的代码会被复制到每个使用它的程序中,因此静态库不需要在程序运行时被单独加载。制作静态库可以帮助你将常用的代码模块化、重用,简化开发过程。…...
PHP在现代Web开发中的高效应用与最佳实践
PHP在现代Web开发中的高效应用与最佳实践 在快速迭代的Web开发领域,PHP作为一门历史悠久且广泛应用的服务器端脚本语言,始终保持着其独特的魅力和强大的生命力。从简单的动态网页到复杂的企业级应用,PHP凭借其易学性、丰富的库支持和广泛的社…...
大数据-134 - ClickHouse 集群三节点 安装配置启动
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...
2024网络安全人才实战能力白皮书安全测试评估篇
9月10日,国内首个聚焦“安全测试评估”的白皮书——《网络安全人才实战能力白皮书-安全测试评估篇》(以下简称“白皮书”)在国家网络安全宣传周正式发布。 作为《网络安全人才实战能力白皮书》的第三篇章,本次白皮书聚焦“安全测…...
[项目][WebServer][解析错误处理]详细讲解
可为每种情况都确实对应一个状态码,当发生错误时,跳转到对应的html页面即可但是为了代码的复用性,可以将所有的错误情况都归置处理 #define SEP ": " #define LINE_END "\r\n" #define WEB_ROOT "wwwroot" #…...
51单片机应用开发---数码管的控制应用
实现目标 1、掌握数码管结构、驱动原理; 2、 一、什么是数码管? 1.数码管定义 数码管,也称为LED数码管,基本单元是发光二极管(LED)。分为七段数码管和八段数码管(多一个小数点DP)。数码管在我们生活中无处不在,比如…...
Vue3+Django5+REST Framework开发电脑管理系统
前端:Vue3TypeScript 后端:Django5REST Framework 功能介绍 用户管理角色管理菜单管理配件管理仓库管理类型管理电脑管理入库管理出库管理库存管理收发明细管理 界面预览 源码地址:managesystem: 电脑管理系统...
Java8函数式接口全攻略
一、接口大白话 1.四大基础接口 Consumer<T> 核心方法:void accept(T t);消费者。接受一个输入参数,不返回任何结果的操作。望文生义:你给我啥,我就执行啥,没有结果。 Supplier<T> 核心方法: T get();供…...
英文软件汉化中文软件教程asi exe dll 等汉化教程
相信大家在使用国际软件的时候,会经常碰到英文类型的软件 或者玩一些游戏使用一些工具,也基本都是外网的,那么对于用户来讲 就会非常的不方便! 小编为大家整理了一些国内大佬出的的英文软件汉化中文软件的视频教程 教程分为EX…...
HTTP 请求方式`application/x-www-form-urlencoded` 与 `application/json` 怎么用?有什么区别?
HTTP 请求方式总结:application/x-www-form-urlencoded 与 application/json 在前后端交互中,客户端发送数据到服务器的常见方式有两种:application/x-www-form-urlencoded 和 application/json。本文将详细介绍这两种请求方式的特点、使用方…...
prometheus 集成 grafana 保姆级别安装部署
前言 本文 grafana 展示效果只需要 prometheus node_exporter grafana 其他的选择安装 环境和版本号 系统: CentOS 7.9 prometheus: 2.54.1 pushgateway: 1.9.0 node_exporter: 1.8.2 alertmanager: 0.27.0 grafana:11.2.0 官网:https://prometheus.io/ 下载地址:h…...
Apache SeaTunnel Committer 进阶指南
Apache SeaTunnel 作为一个开源的数据集成工具,旨在简化和加速海量数据的采集和传输。 社区的 Committer 是指拥有项目存储库的写权限的社区成员,即 Committer 可以自行修改代码、文档和网站,也可以合并其他成员的贡献。成为 Apache SeaTunn…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...
