5.7K Star 41K Fork 22.2K

GVP若依 / RuoYi

 / 详情

登录超时后重新登录时,首次输入正确的验证码会显示错误的问题

已完成
创建于  
2023-03-24 11:40

现象:

当停登录超时后,点击任意需要鉴权的页面或者请求时会弹出 "未登录或登录超时。请重新登录"的提示,点击确定后跳转到login页面, 此时重新输入账号密码, 并输入正确的验证码时, 系统会错误地提示验证码错误.
此时登录超时,弹出窗口,点击确定
输入正确的验证码9
但是会提示验证码错误

环境和配置:

前后端不分离版本
数学类型验证码
chrome浏览器
redis存储session

复现方式:

将session过期时间设为2分钟. 使用chrome浏览器登录系统后等待超过2分钟之后访问任意需要鉴权的菜单,之后输入账号密码验证码可以复现.

原因排查过程:

1.首先查看的历史issues,包括#I4R63Z:验证码问题, #I64DBJ:登录超时后重新登录偶尔出现验证码错误(验证码确认输入正确)等,issues中给出的可能原因是shiro获取的session和web的session不一致.于是从这个方向开始排查,在多处打印两者的session id,发现session id都是一致的. 之后将所有session的获取方式都改成从Shiro获取,验证,发现问题仍然存在,遂排除该原因.

2.后怀疑是否是redis过期时间设置的问题,后经过断点排查,发现逻辑上并不会造成该问题.排除.

3.发现在出现该问题的时候(超时后点击"未登录或登录超时。请重新登录"的确定按钮),之前放在/captcha/captchaImage处打印session id的日志总会出现两次,于是增加日志,打印session中的验证码,发现如下日志:
获取图片验证码的controller
观察日志可以发现,session中确实有正确的验证码9,但是其中又穿插了验证码为4的日志,而且它们的session id都是相同的.再进一步观察时间和线程信息,可以明确,在超时后点击"未登录或登录超时。请重新登录"的确定按钮后,在同一瞬间有两个/captcha/captchaImage请求同时发送到了服务器,它们分别走了不同的线程(线程id为http-nio-8091-exec-9和http-nio-8091-exec-7),它们都使用的同一个session id,操作同一个session中的内容, 出现了并发问题, session中的验证码值在并发操作下可能与前端不一致.这其实也可以解释其他issues中所说的shiro和web的session不一致的情况,两者的seesion是同一个,在同一个方法中,当前一句使用shiro获取seesion并打印验证码的时候是某个值如9,此时刚好另一个请求的线程更改了该session中的验证码,于是下一句使用web session再次获取的时候,已经变成了新的值,不打印session id的话,看上去就像是两者不是同一个session.
红色和蓝色分别为不同的线程,由于两个请求几乎同时到达,它们的日志发生了穿插

为什么会有两个请求?

此时根本原因已经找到,那么为什么会有两个请求呢?打开chrome调试功能,发现当网页超时的时候,在点击确定前,会有一个获取验证码的请求/captcha/captchaImage,但是可以看到该请求暂时未发送(后端代码断点证明这一点).当点击确认后,该请求消失,chrome中会有一个新的/captcha/captchaImage请求完成,而且获取到的验证码时页面上显示的验证码.而在后端断点,也会断到两次请求.
点击确定前会有一个请求出现,但是未发送,点击确定后后端断点会断到两次请求

回忆系统的鉴权方式,系统的鉴权是交给shiro管理的, 在ruoyi中,ShiroConfig中对鉴权失败(包括session过期的情况)后的配置是直接跳转到login页面.
shiro配置
查看login页面,找到了一段js:
login页面
看到window.top.location=window.location基本就有思路了,肯定是这里重新跳转造成了两次请求,这里跳转的过程大概是这样的:1.shiro鉴权失败直接跳转到login页面 -> 2.login页面载入后执行红框中的js -> 3.判断window.top!==window.self为true -> 4.执行window.top.location=window.location造成页面再次重新加载.
这里window.top!==window.self的判断主要是不让Login页面加载在已有的子frame中,如果去掉就会变成这样:
输入图片说明

那么到此就有了结论,在session超时后进行了以上1-4的步骤,在第2步和第4步加载了两次login页面导致/captcha/captchaImage被调用两次.

但是这里就有一个核心问题,第2步和第4步也应该是有先后顺序的,其中第2步时就应该访问/captcha/captchaImage,为什么会在点击确定后在第4步和新的login页面同时请求呢? 初步怀疑可能是chrome的某种优化机制导致的, 百度后发现chrome确实有所谓的"预提取资源机制",此处没有深究,有兴趣的同学可以挖掘.

解决方案

知道原因之后就很好解决了,只要避免两个请求并发执行就行了,方法有很多,我这里一瞬间能想到的就两种:
1.验证码获取改成点击后再获取,这样页面加载时就不会请求验证码.
2.改成滑动验证.
本人本来有将系统验证码改成滑动的计划,这次就刚好改掉了.

但是这里其实也有一个细节,我改成滑动验证码之前也会自然而然想到会不会出现同样的请求两次的问题,于是对AJ-Captcha源码也做了一定的考察.

AJ-Captcha事实上也是在页面加载后运行js获取验证码信息,只不过有一个细节, js的执行顺序是按照页面上声明的顺序来序列化执行的(单线程嘛),把AJ-Captcha中js的引用放在上面alert('未登录或登录超时。请重新登录');这段之后就可以了.另外有点小疑问就是执行window.top.location=window.location跳转后,之前页面后续的js还会不会执行?反正目前没有观察到验证码请求两次的情况了, 如果有懂浏览器的大佬也希望能解答一下.

评论 (3)

万宇一辰 创建了任务

终于找到问题了,每次热部署调试项目时,重启项目后登录第一次总是失败,我都怀疑我的眼睛了,但是我却从来没想过深究这个问题,以为是自己输入错了呢;感谢你给解惑了!!
昨天也是刚换了aj-captcha,比原有的验证码方便多了;之前看登录日志,很多用户都是得错1、2次才能登进去;原有的验证码有时候确实不好识别

我测试了十来遍,还是没有复现你说的问题。你可以用未修改过的最新版本去试试。
输入图片说明

若依 任务状态待办的 修改为已完成

登录 后才可以发表评论

状态
负责人
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
参与者(4)
5494454 liuyu s 1638348698 1151004 y project 1578942802 2231428 wanyuyichen 1578969316
Java
1
https://gitee.com/y_project/RuoYi.git
git@gitee.com:y_project/RuoYi.git
y_project
RuoYi
RuoYi

搜索帮助