解决Shiro jwt并发刷新token问题

2023-10-29

        使用shiro jwt做应用系统的权限校验,网上通用的方式是这样的。

在用户登录时候会生成两份token,一份AccessToken用于返回给前端,前端带上这个令牌去请求后台接口,通常过期时间较短5分钟左右,一份RefreshToken放在Redis中,两个Token的加密值都是当前时间戳,当用户的AccessToken过期时,就去从Redis中通过用户名取得Redis中的RefreshToken,比较AccessToken和RefreshToken中的时间戳是否一致,如果一致就重新生成一组AccessToken和RefreshToken,他们值都是当前时间戳。然后返回给前端。用户继续带着这个新AccessToken请求接口,原则上如果用户持续点击,就可以一致无限的刷新Token,但是这种方式如果页面都是单请求,自然没有问题,但是页面不可避免的都会有并发的请求。如果有并发请求过来,只有第一个请求可以正常返回,其他的后续所有请求都会失败,因为第一个请求已经刷新的Redis中的时间戳,返回给了前端的新的AccessToken,后续的并发请求自然带着老AccessToken就会全部报错。

        这种情况怎么解决呢?

        1. 保证前端请求的顺序执行,这种方式比较消耗前端性能接口请求较慢

        2. 在后端刷新RefreshToken写入Redis时加入Redis写入锁,只有最先最快拿到锁的线程允许修改Redis中的值,锁定时间设置为30s,30s后解锁,写入数据。

        3.缓存旧的AccessToken,每次需要刷新Token的时候都把老Token存储一份,可以放在Redis中,或者一个全局变量中,有效时间30s。也就是说30s内带着老Token请求依然有效

        这里主要详细描述下第三种方式如何实现。

//全局变量用于缓存旧的Token
 Map<String,Long> tempToken = new HashMap<>();
  /**
     * shiro验证成功调用
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        String jwttoken= (String) token.getPrincipal();
        if (jwttoken!=null){
            try{
                if(TokenUtil.verify(jwttoken)){
                    //判断Redis是否存在所对应的RefreshToken
                    String account = TokenUtil.getAccount(jwttoken);
                    Long currentTime=TokenUtil.getCurrentTime(jwttoken);
                    if (RedisUtil.hasKey(account)) {
                        Long currentTimeMillisRedis = (Long) RedisUtil.get(account);
                        if (currentTimeMillisRedis.equals(currentTime)) {
                            return true;
                        }
                    }
                }
                return false;
            }catch (Exception e){
                if (e instanceof TokenExpiredException){
                    System.out.println("AccessToken过期了");
                    if(tempToken.containsKey(jwttoken)){
                        // 判断缓存中是否有token,有的话判断时间是否小于30s,小于30 通过,大于30 清空map
                        Long currentTimeMillis =System.currentTimeMillis();
                        long r = (currentTimeMillis - tempToken.get(jwttoken))/1000;
                        if(r <= 30 ){
                            System.out.println("小于30 所以通过");
                            return true;
                        }else{
                            System.out.println("大于30 所以通过");
                            tempToken.clear();
                            return false;
                        }
                    }
                    if (refreshToken(request, response)) {
                        return true;
                    }else {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 刷新AccessToken,进行判断RefreshToken是否过期,未过期就返回新的AccessToken且继续正常访问
     * @param request
     * @param response
     * @return
     */
    private boolean refreshToken(ServletRequest request, ServletResponse response) {
        String token = ((HttpServletRequest)request).getHeader("X-Auth-Token");
        String account = TokenUtil.getAccount(token);
        Long currentTime=TokenUtil.getCurrentTime(token);
        // 判断Redis中RefreshToken是否存在
        if (RedisUtil.hasKey(account)) {
            // Redis中RefreshToken还存在,获取RefreshToken的时间戳
            Long currentTimeMillisRedis = (Long) RedisUtil.get(account);
            // 获取当前AccessToken中的时间戳,与RefreshToken的时间戳对比,如果当前时间戳一致,进行AccessToken刷新
            if (currentTimeMillisRedis.equals(currentTime)) {
                // 获取当前最新时间戳
                Long currentTimeMillis =System.currentTimeMillis();
                tempToken.put(token,currentTimeMillis);
                RedisUtil.set(account, currentTimeMillis,
                        CommonData.REFRESH_EXPIRE_TIME);
                // 刷新AccessToken,设置时间戳为当前最新时间戳
                token = TokenUtil.sign(account, currentTimeMillis);
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                httpServletResponse.setHeader("Authorization", token);
                httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
                return true;
            }
        }
        return false;
    }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解决Shiro jwt并发刷新token问题 的相关文章

  • 如何使用 IO Codenameone 发布图片/图像

    因为 codenameone 不能使用外部库 HttpConnection 所以我必须使用 Codenameone 提供的内部库 API 只是我已经设法使用 ConnectionRequest 将数据发布到格式化文本 字符串 我想知道是否有
  • 数组查找时间复杂度和。它是如何存储的

    众所周知 通过索引访问数组的时间复杂度是O 1 Java 的文档ArrayList 它由数组支持 对其也有同样的说法get手术 size isEmpty get set iterator 和 listIterator 操作以恒定时间运行 查
  • Spring Batch如何作为Reader读取多个表(查询)并将其写入平面文件写入

    在我的项目中 我读取了具有不同查询的多个表 并将这些结果集合并到平面文件中 我该如何实现这一目标 我的意思是 JdbcReader 直接采用 1 个选择查询 我如何自定义它 如果 JdbcCursorItemReader 不能满足您的需求
  • mvn dependency:analyze 结果不正确

    我一直在寻找一种工具 它能够向您显示未使用的依赖项 我很快就偶然发现了 Maven 命令mvn dependency analyze 这样做的问题是 它经常检测到 未使用的 依赖项 如果缺失 这些依赖项就会导致构建失败 这是优化项目的示例
  • 带有 spring-kafka 的 Kafka 死信队列 (DLQ)

    最好的实施方式是什么死信队列 DLQ Spring Boot 2 0 应用程序中的概念 使用 spring kafka 2 1 x 来处理无法处理的所有消息 KafkaListener某些bean发送到某些预定义的Kafka DLQ主题的方
  • 如何在 Android 中将 EditText 绘制到画布上?

    我想画画 EditText username new EditText context 到我画布上的特定位置 protected void onDraw Canvas canvas 是否可以在基础上画出x y在我的 Java 文件中协调而不
  • Spring Batch:比较数据库之间的数据

    我有两个数据库 Oracle 和 MySQL 目标是将Oracle表中的值保存到MySQL中 要求 MySQL表中不存在数据 但我在理解 Spring Batch 时遇到了困难 步骤中 它包含itemReader itemProcessor
  • 在 Android 中使用 lambdaj

    有人尝试过在android开发中使用lambdaj库吗 当我创建一个简单的小型java应用程序时 它对我来说工作得很好 但我无法在android应用程序中使用它 UPDATE 我正在添加 lambdaj lambdaj 2 3 2 with
  • 让 Java 与 Windows 10 Ubuntu 一起使用

    我安装了 Windows 10 周年更新 以便可以在 Windows 上的 Ubuntu 上尝试 Bash 看如何安装 http www howtogeek com 249966 how to install and use the lin
  • 从外部 clojar 导入/使用资源

    我想做的是将一个大文件 MIDI 声音字体 打包到一个独立的 Maven repo clojar 中 然后能够以编程方式将其拉下来并从单独的项目中使用它 事实证明 这个看似简单的任务比我想象的要复杂 理想的情况是 如果有一种方法可以直接访问
  • 使用 javax.mail 和 CentOS 的邮件服务器

    我有一个 Java 程序 安装在一台旧的 Ubuntu 机器上 并使用 javax mail 发送邮件 然而 那台机器宕机了 我现在在新的 CentOS 机器上运行相同的 Java 应用程序 但是 当我尝试使用 mail smtp host
  • 如何在最短的时间内克隆java中的输入流

    有人可以告诉我如何克隆输入流 并花费尽可能少的创建时间吗 我需要多次克隆输入流以使用多种方法来处理 IS 我尝试了三种方法 但由于这样或那样的原因 事情不起作用 方法 1 感谢 stackoverflow 社区 我发现以下链接很有帮助 并将
  • Java反序列化中避免重复对象

    我有两个列表 list1 和 list2 其中包含对某些对象的引用 其中某些列表条目可能指向同一对象 然后 由于各种原因 我将这些列表序列化为两个单独的文件 最后 当我反序列化列表时 我想确保我不会重新创建超出需要的对象 换句话说 List
  • JFreeChart MeterPlot

    我目前正在用java做Agent项目 在某些时候 我需要显示一个仪表 例如 电池电量 我的程序中有 5 个代理 每个代理都会创建自己的带有名称的仪表图 但不知何故他们没有更新数据集 或者他们正在更新数据集 只是它没有显示在仪表图上 任何想法
  • ObservableList 不更新 ArrayList

    对于学校作业 我们正在使用 JavaFX 中的 ObservableList 对象 对吗 我已经为此工作了一天多了 但无法弄清楚 老师只告诉我们 谷歌一下 所以这也没有帮助 基本上 我们正在开发一个基本的管理应用程序来跟踪人们及其家人 人们
  • Android:如何以编程方式仅圆化位图的顶角?

    我目前正在使用这段代码 Override public Bitmap transform Bitmap source Bitmap result Bitmap createBitmap source getWidth source getH
  • 何时以及为何使用缓冲输入和输出流? [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我读到这些是为了减少磁盘 网络调用开销而使用的 这在写操作的情况下似乎很好 但是进行缓冲读取有什么好处呢 如果您按字节读取文件 则每次都会进
  • 用于将字符串与通配符模式进行匹配的递归函数

    所以我一整天都在试图解决这个作业 只是无法完成 以下函数接受 2 个字符串 第二个 不是第一个 可能包含 的 星号 An 是字符串的替换 空 1个字符或更多 它可以出现 仅在s2中 一次 两次 更多或根本不出现 它不能与另一个相邻 ab c
  • 跳过一行GridBagLayout

    我在 JFrame 上使用 GridBagLayout 我希望能够跳过一两行 但将这些行显示为空白 然后在这些行后面有一个按钮 我在文档中找不到任何方法来执行我所描述的操作 有谁知道我可以执行此操作的任何方法吗 发现它比添加空组件干净得多
  • Spring JMS开始根据请求监听jms队列

    Spring提供 JMSListener用于监听来自特定队列的消息的注释 还有一个替代方案实施JmsListenerConfigurer http docs spring io spring docs current spring fram

随机推荐

  • Python学习笔记(三十五)- 异常设计(Designing with Exceptions)

    1 这个问题是第1章第一次测验的重复内容 看 我告诉你这很容易 为什么 spam 出现在书籍和网络上的这么多Python例子中 困扰我很久的问题终于得到了解答 答 因为Python是以英国喜剧组织Monty Python的名字命名的 基于我
  • 第一节课笔记(环境部署)

    大挖掘 分析与应用展望 大数据指无法在可承受的时间范围内用常规软件工具进行捕捉 管理和处理的数据集合 是需要新处理模式才能具有更强的决策力 洞察发现力和流程优化能力的海量 高增长率和多样化的信息资产 数据挖掘 Data Mining 是有组
  • python远程连接ssh_Python实现SSH连接远程服务器

    首先需要安装paramiko模块 coding utf 8 author MuT6 Sch01aR import paramiko ssh paramiko SSHClient 创建SSH对象 ssh set missing host ke
  • STM32电源框图解析(VDD、VSS、VDDA、VSSA、VREF+、VREF-、VBAT等的区别)】

    VCC 电路的供电正电压 VDDD 芯片的工作数字正电压 GND 电路的供电负电压 VSSD 芯片的工作数字负电压 VDD 芯片的工作正电压 VREF ADC基准参考正电压 VSS 芯片的工作负电压 VREF ADC基准参考负电压 VDDA
  • setAccessible(true)安全检查不通过 Bean转Map

    setAccessible true 安全检查不通过 Bean转Map public static Map
  • 自己搭建IntelliJ IDEA授权服务器

    尊重原创 原文链接 感谢 首先说明 服务器文件不是我写的 我也是从lanyu大神那里搬过来的 我这里只是详细介绍一下如何使用 lanyu大神博客 http blog lanyus com archives 174 html 下载服务器文件
  • 数据库协议

    1 TDS协议 参考链接 https blog csdn net u014608280 article details 80776703 https blog csdn net cxzhq2002 article details 57203
  • [Err] 1064 - You have an error in your SQL syntax; checkthe manual that corresponds...解决方法总结与分析

    Err 1064 You have an error in your SQL syntax checkthe manual that corresponds to your MySQL serverversion for the right
  • windows命令行获取文件夹内所有文件列表

    命令 dir dir 获取帮助信息结果如下 显示目录中的文件和子目录列表 DIR drive path filename A attributes B C D L N O sortorder P Q R S T timefield W X
  • cocos creator 两个刚体的碰撞检测

    想做玩家和障碍物的碰撞阻拦 需要给玩家和障碍物添加刚体 但是添加之后还是会穿透 百度后发现玩家的移动不能通过设置坐标实现的 必须要设置刚体速度的方式来实现玩家移动 1 给玩家 障碍物添加刚体 会看到添加了RigidBody和PhysicsB
  • 启动sonarqube 报错 AccessDeniedException: /opt/module/sonar/sonarqube-7.5/temp/conf/es/elasticsearch.yml

    报错信息 WrapperSimpleApp Encountered an error running main java nio file AccessDeniedException opt module sonar sonarqube 7
  • vue的拖拽插件: vue.draggable

    中文文档地址 vue draggable中文文档 itxst comVue Draggable是一款基于Sortable js实现的vue拖拽插件 支持移动设备 拖拽和选择文本 智能滚动 可以在不同列表间拖拽 不依赖jQuery为基础 vu
  • 2023-2024最新python毕业设计选题推荐大全

    文章目录 0 前言 1 python 算法类 毕设选题 2 python 数据挖掘 毕设选题 3 python 大数据处理 云计算 区块链 毕设选题 4 python 网络安全 毕设选题 5 python 游戏设计 动画设计类 毕设选题 适
  • LeetCode 7. 整数反转

    题目链接 7 整数反转 注意题目已经更改为不允许存储 64 位整数 有符号或无符号 class Solution public int reverse int x int total 0 while x if total gt 0 tota
  • H5项目怎么打包成APP

    文章目录 前言 一 新建5 APP项目 二 删除不需要的文件 三 将H5打包的文件拷贝到当前目录下 四 配置APP 五 发行 云打包 六 安装apk 总结 前言 开发uni app的编辑器HBuilderX可以将H5项目打包成APP 相信很
  • 牛客SQL大厂面试真题目录

    1 某音短视频 SQL156 各个视频的平均完播率 SQL157 平均播放进度大于60 的视频类别 SQL158 每类视频进一个月的转发量 率 SQL159 每个创作者每月的涨粉率及截止当前的总粉丝量 SQL160 国庆期间每类视频点赞量和
  • etc的常见算法_ETC面试题总结.doc

    ETC面试题总结 ETC 面试 ETC 面试1 一 测试2 二 UNIX4 三 Oracle7 四 智能网方面10 五 C 方面11 六 网络13 七 操作系统16 八 数据结构17 九 其它19 测试 软件在开发过程中的测试流程 讲一下软
  • Sublime Text 3配置Go语言开发环境

    Sublime Text 3配置Go语言开发环境 1 Go语言环境搭建 2 GoSublime安装和配置 2 1 安装步骤 2 2 代码开发 2 3 编译运行 1 Go语言环境搭建 本篇博文是在读者Go自身环境已经搭好 Sublime Te
  • 华为OD机试 - 求最小步数(Java)

    题目描述 求从坐标零点到坐标点n的最小步数 一次只能沿横坐标轴向左或向右移动 2 或 3 注意 途径的坐标点可以为负数 输入描述 坐标点n 输出描述 输出从坐标零点移动到坐标点n的最小步数 备注 1 lt n lt 10 9 用例 输入 4
  • 解决Shiro jwt并发刷新token问题

    使用shiro jwt做应用系统的权限校验 网上通用的方式是这样的 在用户登录时候会生成两份token 一份AccessToken用于返回给前端 前端带上这个令牌去请求后台接口 通常过期时间较短5分钟左右 一份RefreshToken放在R