若依框架中spring security的完整认证流程,及其如何使用自定义用户表进行登录认证,学会轻松实现二开,嘎嘎赚块乾
1)熟悉之前的SysUser登录流程
过滤器链验证配置
这里security过滤器链增加了前置过滤器链jwtFilter
该过滤器为我们自定义的,每次请求都会经过jwt验证
ok我们按ctrl alt B跳转过去来看下
首先会获取登录用户LoginUser
内部通过header键,获取token,再将自定义前缀Bearer替换为空获得jwtToken
以上自定义的header键和token前缀都为配置类里配置并且通过@Value注入
总体流程
前端的request-Hearder->Authentication:Bear erzcxvgGDS234…-> erzcxvgGDS234.-> login_user_key:用户uuid ->login_tokens->LoginUser
由于刚登录,LoginUser为空,直接放行,然后遇到刚才设置的security白名单permitAll继续放行,来到我们的login接口
authenticate调用后跳转到如下的loadUserByUserName方法
传入的String username为前面authenticationToken里的username
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
1.数据库查用户,判断存不存在,然后密码passwordService.validate(user);验证。 (大概就是字节数组长度比较及其内部每个元素的比较,如果一个不一致则匹配错误)
根据用户输入的密码进行加密(应该和md5比对差不多。和数据查出来,以下为不详细的解释,不想看可以跳过
(按位或,00为0,01为1 一个不为0则为该值 一票否决
异或 aa为0 ,ab为1,二者不一致则为1,一票否决)
右移原理
1.有符号右移,32位补码二进制(为什么是补码?计算机存负数没有负号标识’-',使用取反0,变1,1变0,然后+1获得补码,而整数补码,原码,反码全一一样) 最右边砍掉一个,然后最左边也就是高位,根据正数补0,负数补1,
例子
-4
的原码为00000000 00000000 00000000 00000100
。反码为
11111111 11111111 11111111 11111011
。补码为
11111111 11111111 11111111 11111100
。
无符号右移>>>31位 只剩下一个1,左侧始终补0 有符号则负1正0 所以结果为1 因此所有负数无符号右移,结果都为1 i*1得i本身,indexB等于i,在数组ab里相同下标进行异或比较 结果我们称之为Z,
a[],b[]异或的结果Z再与result值按位或
如果在前面的lenA-lenB不为0,那么为其长度的差值result,此时再与Z按位或的结果 结果不为0,假设result=lenA-lenB=1. 1|3(a[]^b[]) 结果为3.
也就是相当于监听,数组里的元素 异或结果是否一致,一致为0,同时长度必须一致,否则按位或结果不为0,一旦长度还是数组元素内容不一致,那么result会不为0,此时最终结果result==0为false,密码错误
补充
异或在没有进位的情况下可以看作加法
加法示例:
01 异或10 = 1+ 2 =3
,在二进制中为11
,产生了进位。异或示例:
01 ^ 01 = 01 不能看作1+1=2
,没有进位
ok如果这一步没有抛出密码错误异常 或者重试次数超出异常,就可以根据我们的自定义SysUser实体进行创建security登录实体了,这里的permission为数据库多表查询的权限集合,不多说了,之前文章有提及
将其以UserDetails类型存到authentication里的principal里,然后进行token创建
你会不会好奇,为什么authenticate就能到UserServiceImpl的loadUserByusername方法呢?然后为什么他最后返回的值明明是LoginUser(多态类型为接口类型UserDetails) 但是却是以authentication的principal接收的?
让我们来看下AuthenticationManager.authenticate做了什么,先看下一张网上摘下来的流程图
进入usernamePasswordAuthenticationFilter也就是自带的那个链,这里我们使用了自定义的过滤器(原版的有局限性好像),LoginService
封装前端用户提交的用户名和密码到实现了Authentication的UsernamePasswordAuthenticationToken里,username和password
也就是这个
2.然后将其放进入了上下文
3.然后将实现了Authentication的UsernamePasswordAuthenticationToken这个玩意作为形参调用AuthenticationManager的authenticate方法
图中说明是调用了一群Provider中的authenticate进行认证
就这一步迷迷糊糊的对吧,我们来看下这个Manager到底是什么,
4.剖析AuthenticationManager
注入的?,说明搁哪个config配置类里的@Bean藏着呢,在config包下

new了一个Daoprovider 这里传入了一个 userDetailService和设置了密码认证方式
最后new了一个ProviderManger将这个Dao传入,和图片的流程一样

也就是说我们authenticate的调用这个ProviderManager管理器里的provides的方法 AuthenticationManager就是一个接口,一个规范而已,
好家伙BYD吐槽一下 这里面的套路,以后这种接口都是假的,直接找谁实现他的就行,真正干活的在那个哥们身上
provider管理器调用了我们传入的daoProvider的authenticate方法
我们来找找authenticate,不过DaoAuthentProvider上没有这个authenticate,在他的爸爸那里
他会去调用一个叫retriveUSER的方法,多传了一个username和我们前端发来的那个usernamePasswordToken…卧槽,这名字真特么长都不想打了,泪目了
byd又搞了一个抽象方法,由刚才的DaoAuthentProvider实现, 好家伙跑来跑去的,贼难受(目前功力不够,不太清楚这样做的目的,强制子类实现的一种接口?)
先搞了一个prepareTimingAttach 顾名思义防止时间攻击,不看了,看下面的
byd终于看到他们的联系了,调用刚才set进来的自定义验证器的load…方法,由我们自己重写实现,然后返回一个UserDetails给authenticate调用
这里也是我一开始开始出现疑惑的地方,就是为什么authenticate调用loadByuserName后返回的UserDetails用的是authentication接收的
这里在retrieve取回 user的时候还进行了一个确认用户名和查看缓存,事无巨细了属于是

然后将这个loadUser出来的user进行预校验,具体就是,new一个校验对象,然后身上的一个check方法调用userDetails自身需实现的isAccount。。is…方法,为什么这里不直接user.isxx调用呢
然后又additionalAuthenticationChecks 额外的检查,用到了刚才传进去的密码校验器
catch部分我们就不看了,算了还是看一下吧
又校验了一次,不过必须是前面从缓存拿到的的才行,否则用我们之前的retrieve其值为false,相当于一个名刀 (第二条命)
然后提交校验,查看密码凭证是否过期,又自定义属性类的实现决定
最后的最后!
这个crentials目前不指定在哪会用到,这个authorities在下次jwt过滤器会进行设置,
okok此时我们终于搞清楚了authenticate返回值及其工作大概原理了,
继续往下
一个是创建jwt token字符串,一个是给他干到redis里
login_user_key:uuid (map)->作为claims + 私钥+ 算法 压成一个token字符串 前面好像说过了sorry
登录时间,过期时间,这里的getToken为前面随机生成的uuid,然后getTokenKey在这个uuid前再拼一个用户uuid,那个LoginUser的token属于jwt的,而这个返回的userKey作为redis缓存里的
登录结束。
稍等,带我打个断掉debug理一下流程,感觉好长一条线
1.security配置类,配置好jwt过滤器和login白名单,(否则登录接口不允许访问,因为此时context为空还没有进行设置,后续jwt过滤器验证通过会往SecurityContext设置一个authentication)
2.jwt过滤,获取请求头的JWTtoken->
-
有token,解析token为claims 根据自定义键值获取其中的uuid,根据自定义redis键值+uuid获取 Redis里的LoginUser实体
验证有效期,封装authenticationToken设置上下文context,标识身份验证通过,如果缺少这一步,jwt放行后会结果security认证,context为空返回401,自定义认证失败异常,不细说,自查阅 -
没token,如果是login。jwt先放行,login白名单,放行,然后进入认证逻辑Loginservice
3.封装username和password给upat(usernamePasswordAuthenticationToken),设置上下文,后续获取该password与数据库的加密比对
4.调用authenticationManager(也就是Provider,).authenticate(派发任务给daoProviders->父类AbstractUserDetailsAuthenticationProvider)方法,传入upat
5.调用retrieve,方法,内部调用传入的UserDetailsService的loadUsername方法
6.查库,密码验证,返回Usertails对象
7.UserDetails二次校验,封装authentication返回
8.记录redis和jwtToken 生成返回前端记录
简短版
jwt过滤器链验证,security认证(userpasswordAuthenticationFilter/自定义的loginService),Providers调用loadUser, loadUser返回值userDetails存redis和生成token
画一个流程图看看
2)我们如果想实现多用户表呢?
2.1)设置自定义用户实体
public class DamingUser extends BaseEntity {private Long userId;private String username;private String password;public String getUsername() {return username;}public Long getUserId() {return userId;}public void setUserId(Long userId) {this.userId = userId;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}
2.2)LoginUser定义
由于后续需要将自定义实体设置给LoginUser,需要定义对应的构造函数,方便后面验证成功后设置值,然后全文调用
2.3)自定义一个manager Bean注入
因为我们是用自己的业务类判断的嘛,因此我们要找到调用loadUserByUsername那家伙,
也就是AuthenticationManager(ProviderManager),
给他设置provider的时候,set我们自己的UserDetailServiceImpl
在security config里新搞一个Manager Bean 并且起名字,后续使用Qualifier指定哪个bean,然后原先若依框架的UserDetailImpl类和Authenticationmanager上加@Primary, 否则会报冲突
/*** 自定义身份管理器验证实现*/@Autowired@Qualifier("StudentDetailsServiceImpl")private StudentDetailsServiceImpl studentDetailsService;@Bean("StudentAuthenticationManager")public AuthenticationManager studentAuthenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();daoAuthenticationProvider.setUserDetailsService(studentDetailsService);daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());return new ProviderManager(daoAuthenticationProvider);}
2.4)自定义UserDetailsService
@Component("StudentDetailsServiceImpl")
public class StudentDetailsServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println("过来了!");// new LoginUser(user.getUserId(), user.getDeptId(),// user, permissionService.getMenuPermission(user));return null;}
}
2.5)接口service注入使用
照搬
@Component
public class StudentLoginService {@Resource@Qualifier("StudentAuthenticationManager")private AuthenticationManager authenticationManager;Authentication authentication = null;@Autowiredprivate TokenService tokenService;public String login(String username, String password) {try {UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(username, password);AuthenticationContextHolder.setContext(authenticationToken);// 该方法会去调用StudentsDetailsServiceImpl.loadUserByUsernameauthentication = authenticationManager.authenticate(authenticationToken);} catch (Exception e) {if (e instanceof BadCredentialsException) {AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));throw new UserPasswordNotMatchException();} else {AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));throw new ServiceException(e.getMessage());}} finally {AuthenticationContextHolder.clearContext();}AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();// 生成tokenreturn tokenService.createToken(loginUser);}}
2.6)接口调用
@RestController
@RequestMapping("/quiz/student/user")
public class StudentUserController {@Autowiredprivate StudentLoginService studentLoginService;@PostMapping("/login")public AjaxResult login(StudentLoginBody studentLoginBody) {AjaxResult ajax = AjaxResult.success();System.out.println("接入了");String token = studentLoginService.login(studentLoginBody.getUsername(), studentLoginBody.getPassword());ajax.put(Constants.TOKEN, token);return ajax;}
}
2.7测试
漂亮!后续不用多介绍了,cv了懂得懂得,然后生成token给前端然后存redis,前端拿来解析查redis,有就判断是否过期然后不过期就设置上下文, 过期了直接报错okok结束结束
相关文章:

若依框架中spring security的完整认证流程,及其如何使用自定义用户表进行登录认证,学会轻松实现二开,嘎嘎赚块乾
1)熟悉之前的SysUser登录流程 过滤器链验证配置 这里security过滤器链增加了前置过滤器链jwtFilter 该过滤器为我们自定义的,每次请求都会经过jwt验证 ok我们按ctrl alt B跳转过去来看下 首先会获取登录用户LoginUser 内部通过header键,获…...

selenium:操作滚动条的方法(8)
selenium支持几种操作滚动条的方法,主要介绍如下: 使用ActionChains 类模拟鼠标滚轮操作 使用函数ActionChains.send_keys发送按键Keys.PAGE_DOWN往下滑动页面,发送按键Keys.PAGE_UP往上滑动页面。 from selenium import webdriver from se…...

Discuz | 起尔开发 传奇开服表游戏公益服发布论坛网站插件
Discuz | 起尔开发 传奇开服表游戏公益服发布论坛网站插件 插件下载:源码 - 起尔开发的插件下载 演示地址:discuz.72jz.com 标黄和非标黄自动分开 在标黄时间内显示在上面置顶,标黄过期后自动显示在下面白色区域。 后台可以设置非标黄默认…...
问:JAVA对象的数据结构长啥样?
Java 对象在内存中的结构是一个复杂且精细的设计,它不仅关乎对象如何存储,还直接影响到垃圾回收(GC)、并发控制等运行时行为。一个典型的 Java 对象主要由三部分组成:对象头(Object Header)、实…...

STGCN解读(论文+代码)
一、引言 引言部分不是论文的重点,主要讲述了交通预测的重要性以及一些传统方法的不足之处。进而推出了自己的模型——STGCN。 二、交通预测与图卷积 第二部分讲述了交通预测中路图和图卷积的概念。 首先理解道路图,交通预测被定义为典型的时间序列预测…...
perl读取目录,写入文件
perl读取目录,写入文件 此脚本有两个输入参数,第一个参数为需要打印的文件目录,第二个参数为打印后的文件名; 该脚本名称为out_file_full_path #!/bin/perluse 5.010; my $dir $ARGV[0]; # 此为第一个参数; opendi…...

JDK-23与JavaFX配置在IDEA中
一、安装 1.IDEA安装,可以查看CSDN 2.JDK,JavaFX安装,可以查看CSDN 二、配置JDK 打开IDEA,选择个项目,点击图中的设置按钮: 点击项目设置: 点击“”添加JDK,寻找相应的JDK目录就行 三、配置…...

VSCode运行QT界面
VSCode用久了,感觉Qt Creator的写起代码来还是不如VSCode得心应手,虽然目前还是存在一些问题,先把目前实现的状况做个记录,后续有机会再进一步优化。 当前方式 通过QtCreator创建一个CMake项目,然后使用CMake的方式在VSCode中进行编译。 claude给出的建议 左上角的名字会…...
npm-run-all 使用实践
参考: npm-run-all 背景 在前端开发中,你是否存在以下烦恼: 写 package.json 的 scripts 命令时,命令太过冗长,例如编译命令 build 需要执行清理 clean, 编译css build:css, 编译js build:js, 编译html build:html 命令,则 bui…...
【CCPC】The 2021 CCPC Guilin Onsite (XXII Open Cup, Grand Prix of EDG) K
Tax #图论 #最短路 #搜索 #暴力 题目描述 JB received his driver’s license recently. To celebrate this fact, JB decides to drive to other cities in Byteland. There are n n n cities and m m m bidirectional roads in Byteland, labeled by 1 , 2 , … , n 1,…...
selenium的实际使用
1.标签页的切换 #获取当前所有的窗口 curdriver.window_handles #根据窗口索引进行切换 driver.switch_to.window(cur[1]) from selenium import webdriverimport timedriver webdriver.Chrome()driver.get(http://www.baidu.com)time.sleep(1)eledriver.find_element_by…...

OpenShift 4 - 云原生备份容灾 - Velero 和 OADP 基础篇
《OpenShift 4.x HOL教程汇总》 说明: 本文主要说明能够云原生备份容灾的开源项目 Velero 及其红帽扩展项目 OADP 的概念和架构篇。操作篇见《OpenShift 4 - 使用 OADP 对容器应用进行备份和恢复(附视频) 》 Velero 和 OADP 包含的功能和模…...

javaWeb项目-Springboot+vue-校园论坛系统功能介绍
本项目源码(点击下方链接下载):java-springbootvue-xx学校校园论坛信息系统实现源码(项目源码-说明文档)资源-CSDN文库 项目关键技术 开发工具:IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架:ssm、Springboot…...

centors7升级GLIBC2.18
错误来源:找不到GLIBC2.18,因为glibc的版本是2.17 网上大多教程方法,反正我是行不通: 方法1:更新源,然后使用yum安装更新 方法2:下载源码,configrue,make执行 wget h…...
基于深度学习的异常检测
基于深度学习的异常检测是一项重要的研究领域,主要用于识别数据中的异常样本或行为。异常检测广泛应用于多个领域,如网络安全、金融欺诈检测、工业设备预测性维护、医疗诊断等。传统的异常检测方法通常依赖于统计分析或规则,但随着数据复杂性…...
深入理解 SQL 中的高级数据处理特性:约束、索引和触发器
在 SQL(Structured Query Language)中,除了基本的查询、插入、更新和删除操作外,还有一些高级的数据处理特性,它们对于确保数据的完整性、提高查询性能以及实现自动化的数据处理起着至关重要的作用。这些特性包括约束、…...
IC验证面试中常问知识点总结(七)附带详细回答!!!
15、 TLM通信 15.1 实现两个组件之间的通信有哪几种方法?分别什么特点? 最简单的方法就是使用全局变量,在monitor里对此全局变量进行赋值,在scoreboard里监测此全局变量值的改变。这种方法简单、直接,不过要避免使用全局变量,滥用全局变量只会造成灾难性的后果。 稍微复…...

【前端】如何制作一个自己的网页(8)
以下内容接上文。 CSS的出现,使得网页的样式与内容分离开来。 HTML负责网页中有哪些内容,CSS负责以哪种样式来展现这些内容。因此,CSS必须和HTML协同工作,那么如何在HTML中引用CSS呢? CSS的引用方式有三种࿱…...
Java之模块化详解
Java模块化,作为Java 9引入的一项重大特性,通过Java Platform Module System (JPMS) 实现,为Java开发者提供了更高级别的封装和依赖管理机制。这一特性旨在解决Java应用的封装性、可维护性和性能问题,使得开发者能够构建更加结构化…...

HTB:Knife[WriteUP]
目录 连接至HTB服务器并启动靶机 1.How many TCP ports are open on Knife? 2.What version of PHP is running on the webserver? 并没有我们需要的信息,接着使用浏览器访问靶机80端口 尝试使用ffuf对靶机Web进行一下目录FUZZ 使用curl访问该文件获取HTTP头…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...