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…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...