Token 刷新并发处理解决方案

2023-10-27

对 Token 进行刷新续期,我们要解决并发请求导致重复刷新 Token 的问题,这也是设计刷新 Token 的难点。这里我会分别介绍前端和后端各自的处理方案。

后端方案:利用 Redis 缓存

当同时发起多个请求时,第一个接口刷新了 Token,后面的请求仍然能通过请求,且不造成 Token 重复刷新。那么,后端在用户第一次登录时,需要将生成的 Token 数据(token 和 createTime)缓存一份到 Redis 中。

当 Token 过期时,重新生成新的 Token 数据并更新 Redis 缓存,同时在 Redis 中设置一条 Token 过渡数据并设置一个很短的过期时间(比如 30s)。如果后面的请求发现 Token 已经被刷新了,就判断 Redis 中是否存在 Token 过渡数据,存在就放行,这样同一时间的请求都可以通过。

源码地址:https://github.com/yifanzheng/spring-security-jwt/tree/refresh-token-redis

Token 刷新流程图

在这里插入图片描述

前端方案:请求拦截

由于前端请求都是异步的,只有一个请求的时候,刷新 Token 是比较好处理的,但并发请求下刷新 Token 处理起来有点麻烦。我们需要考虑在多个请求几乎同时发起并且 Token 都失效的情况,当第一个请求进入 Token 刷新流程时,其他请求必须等待第一个请求完成 Token 刷新后再使用新 Token 进行重试。
简单地讲,就是同一时间有多个请求且 Token 都失效,在第一个请求进行 Token 刷新时,其他请求必须处于等待状态,直到 Token 刷新完成,才能携带新 Token 进行重试。

下面,我使用了 Angular 的请求拦截器,利用 BehaviorSubject 进行 Token 刷新状态的监听,当 Token 刷新成功,放行后面的请求进行重试。

除此之外,前端还可以利用 Promise,将请求存进队列中后,同时返回一个 Promise,让这个 Promise 一直处于 Pending 状态(即不调用 resolve),此时这个请求就会一直等待,只要我们不执行 resolve,这个请求就会一直在等待。当刷新 Token 的请求完成后 ,我们再调用 resolve,逐个重试。

Github 地址:https://github.com/yifanzheng/spring-security-jwt/tree/refresh-token-frontend

Angular 代码示列

import { Injectable } from "@angular/core";
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpErrorResponse
} from "@angular/common/http";
import { throwError, Observable, BehaviorSubject, of } from "rxjs";
import { catchError, filter, finalize, take, switchMap, mergeMap } from "rxjs/operators";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  private refreshTokenInProgress = false;
  private refreshTokenSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!req.headers.has("Content-Type")) {
      req = req.clone({
        headers: req.headers.set("Content-Type", "application/json")
      });
    }
    // 统一加上服务端前缀
    let url = req.url;
    if (!url.startsWith('https://') && !url.startsWith('http://')) {
      url = "./" + url;
    }
    req = req.clone({ url });
    req = this.setAuthenticationToken(req);

    return next.handle(req).pipe(
      mergeMap((event: any) => {
        // 若一切都正常,则后续操作
        return of(event);
      }),
      catchError((error: HttpErrorResponse) => {
        // 当是 401 错误时,表示 Token 已经过期,需要进行 Token 刷新
        if (error && error.status === 401) {
          if (this.refreshTokenInProgress) {
            // 如果 refreshTokenInProgress 为 true,我们将等到 refreshTokenSubject 是 true 时,才可以再次重试该请求
            // 这表示刷新 Token 动作已完成,新 Token 已准备就绪
            return this.refreshTokenSubject.pipe(
              filter(result => result),
              take(1),
              switchMap(() => next.handle(this.setAuthenticationToken(req)))
            );
          } else {
            this.refreshTokenInProgress = true;
            // 将 refreshTokenSubject 设置为 false,以便后面的请求调用时将处于等待状态,直到检索到新 Token 为止
            this.refreshTokenSubject.next(false);
            return this.refreshToken().pipe(
              switchMap((newToken: string) => {
                this.refreshTokenSubject.next(true);
                // 重新设置新的 Token
                localStorage.setItem("token", newToken);
                return next.handle(this.setAuthenticationToken(req));
              }),
              // 当刷新 Token 请求完成后,需要将 refreshTokenInProgress 设置为 false,用于下次刷新 Token
              finalize(() => (this.refreshTokenInProgress = false))
            );
          }
        } else {
          return throwError(error);
        }
      })
    );
  }

  private refreshToken(): Observable<any> {
    // 这里需要换成实际的 Token 刷新接口
    return of("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzdGFyIiwicm9sZSI6WyJST0xFX1VTRVIiXSwiaXNzIjoic2VjdXJpdHkiLCJpYXQiOjE2MDY4MjczMDAsImF1ZCI6InNlY3VyaXR5LWFsbCIsImV4cCI6MTYwNjgzNDUwMH0.Hiq2DsH6j4XFd_v87lDWGlYembTLck7DjMLRLWdyvOo");
  }

  private setAuthenticationToken(request: HttpRequest<any>): HttpRequest<any> {
    return request.clone({
      headers: request.headers.set("Authorization", "Bearer " + localStorage.getItem("token"))
    });
  }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Token 刷新并发处理解决方案 的相关文章

  • 面试题(5)

    1 介绍STL STL 标准模板库 由容器算法迭代器组成 vector实质上就是一个动态数组 会根据数据的增加 动态的增加数组空间 vector使用allocator来进行内存管理 使用3个迭代器来引用这段内存 vector的iterato
  • 10分钟教你写一个数据库

    今天教大家借助一款框架快速实现一个数据库 这个框架就是Calcite 下面会带大家通过两个例子快速教会大家怎么实现 一个是可以通过 SQL 语句的方式可以直接查询文件内容 第二个是模拟 Mysql 查询功能 以及最后告诉大家怎么实现 SQL
  • Java中存储金额的数据类型java.math.BigDecimal

    在数据库中存储金额的数据类型通常用的是 decimal 20 2 在java中与之对应的数据类型并不是double 而是java math BigDecimal 上例子 Test public void testBigDecimal dou
  • FPGA学习:TMDS协议编码与解码测试

    TMDS协议是当下很重的一个视频传输协议 DVI以及HDMI均采用该协议进行视频数据的转化和传输 TMDS编码部分如下 通过该模块可将8bit视频像素数据转为10bit 具体的转化流程图可自行搜索 算法并不复杂 Testbench time
  • 常见的大型软件项目开发文件目录结构

    常见的大型软件项目开发文件目录结构 1 Java 项目 调试阶段编译后的 class 文件放到 classes 目录 将 classes 目录和 lib 中的其他工具 jar 放到 classpath 中 运行当前目录是项目根目录 正式发行
  • 动漫头像生成如此简单,教你接口快速制作动漫头像

    动漫头像生成如此简单 教你如何快速制作动漫头像 想生成这样的动漫头像吗 告诉你 很简单 这里以nodejs作为示例 第一步 引入腾讯人脸转换sdk npm install tencentcloud sdk nodejs ft save 第二
  • HDU 1599(floyd)(求最小回路问题)

    find the mincost route Problem Description 杭州有N个景区 景区之间有一些双向的路来连接 现在8600想找一条旅游路线 这个路线从A点出发并且最后回到A点 假设经过的路线为V1 V2 VK V1 那
  • windows excel 的一些官方模板

    中文版 https templates office com zh cn templates for Excel 日文版 https templates office com ja jp templates for Excel
  • 10个问题解答火热的元宇宙概念

    这篇转自金色财经平台的文章涵盖了我们近期文章中所讨论过的大部分关于元宇宙的内容 虽然不甚全面 但胜在系统 虽然现在探讨最终应用为时甚早 但各行各业开始想元宇宙靠拢已是不争 让我们通过十个由点及面浅层问题 一同简单的回顾下元宇宙伊始至今都经历
  • easyexcel 数据量过大问题

    java总的生成excel工具 poi太费事了 于是就是用来 阿里提供的 easyexcel 很简单 具体使用方式参考 https zhuanlan zhihu com p 88720415 utm source wechat timeli
  • WebSocket协议及优点(总结)

    参考文章 https www zhihu com question 20215561 参考书籍 图解HTTP 这篇文章通过http的瓶颈 引出ajax轮询以及长轮询 最后说明为什么使用websocket以及他的原理 当我们使用http协议探
  • 2020-09-18

    python的subprocess模块 在python文件中想调用spm encode命令对一个句子进行分词 就需要使用subprocess模块在python中执行shell命令 但是网上其他的给出的都是传递文件的 或者把字符串传递给cmd
  • 从“二义性”谈到企业数字化工作开展

    中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要 中提到 加快数字化发展 建设数字中国 自此各行各业开始投身各自领域的数字化转型中 但什么是数字 数字化又是什么 转型之于企业究竟意味着什么 我们暂且不用学术语言来诠释
  • centos8安装使用nc

    转自 https linuxconfig org install netcat on redhat 8 README nc 是什么 nc 全名叫netcat 它可以用来完成很多的网络功能 譬如端口扫描 建立TCP UDP连接 数据传输 网络
  • 【读书笔记】Principles and practices of interconnection networks 第一章笔记

    互联网络导论 互联网络的三个问题 数字系统的三个基本构件 数字系统由3个基本构件组成 逻辑 logic 内存 memory 通信 communication 本书关注点 通信 这本书所关注的是数字系统的通信 因为随着技术的进步 处理器与存储
  • 关于matlab提示“警告: 矩阵为奇异工作精度” 的问题

    在自己编写基于手写数字多特征数据集的机器学习中的最小风险贝叶斯算法过程中 总是提示 警告 矩阵为奇异工作精度 虽然知道问题出在 R 1 k i j 1 2 test 1 k i s e ave 1 j 1 inv sigma 1 j tes

随机推荐

  • Java中的类(Object类、Data类、Objects类、DataFormat类、Calender类、String和StringBuilder类、包装类)

    一 Object类 1 Object类介绍 Object是所有类的父类 一个类都会直接或者间接继承该类 该类中提供了一些非常常用的方法 2 toString 方法 作用 打印对象的信息 重写前 打印的是包类名 地址值 重写后 打印的是对象中
  • python-selenium自动化-窗口的切换/过期

    1 过期 当页面来回去切换的时候 必须重新获取 否则报错过期 必须重新获取一次 避免ABA的发生 代码案例 from selenium import webdriver from selenium webdriver common by i
  • 虚拟机下ubuntu上网

    在配置的时候 虚拟机有三种方法上网 一个是NAT 一个是host only 一个是bridge 我主要是试过两种 简单的介绍一下 我是xp下装的虚拟机 虚拟机里面是redhat就简单的用redhat代替虚拟机 xp代替真机 NAT 方法 它
  • (Ext基础篇) Ext核心组件

    Ext Componet 概述 所有Ext组件的基类 所有Componet的子类都自动地参与标准的Ext组件生命周期 创建 渲染 销毁 这些操作 由Container 类提供 在创建容器时 组件可以通过items 配置选项被添加到容器中 或
  • 计算机屏幕出现蓝色条,电脑显示器出现一道蓝条怎么回事

    公告 为响应国家净网行动 部分内容已经删除 感谢读者理解 话题 电脑显示器出现一道蓝条怎么回事 回答 瑕疵屏 有坏点坏线 这就是次品了 要是使用一段时间后出现的 就是液晶面板排线出焊接问题 硬件故障 很难修理 除非有专业设备 参考回答 电脑
  • 同时连接内外网教程

    一台电脑可以同时上内外网 省去了办公的许多烦恼 每台电脑都有自己的路由表 可以通过开始 运行 输cmd 输入route print 就可查询到自身的路由信息 计算机上的路由表可以手动进行修改 添加或删除 这样就可以通过一系列的设置来满足同时
  • 机器学习算法基础概念学习总结

    1 基础概念 1 10折交叉验证 英文名是10 fold cross validation 用来测试算法的准确性 是常用的测试方法 将数据集分成10份 轮流将其中的9份作为训练数据 1分作为测试数据 进行试验 每次试验都会得出相应的正确率
  • ST-LINK烧录stm32程序步骤

    此篇文章记录下STM32使用ST LINK烧录程序的步骤 好兄弟 给个关注呗 材料 ST LINK驱动安装 点我下载st link驱动 提取码 4aw1 安装包有详细教程 stm32f103c8t6 ST LINK 杜邦线 keil5配置
  • vscode搭建esp32(避坑小窍门)

    这里写自定义目录标题 1 编译过程出现 终端将被任务重用 按任意键关闭 错误 可能的解决办法 1 在 终端 配置任务 PlatformIO Test 以Test工程名为例 打开tasks json文件 2 添加 presentation e
  • 02-离散仿真引擎基础

    Homework02 简答题 1 解释游戏对象 GameObjects 和资源 Assets 的区别与联系 区别 游戏对象是具有一定属性与功能的类的实体化 对应为Unity中具有对应职能与属性的组件 例如游戏中常见的玩家 怪物等 资源是预先
  • react 引入antd 样式_引用Ant.Design到React中

    参考地址 http ant design index cn ant design 首先说说ant design是干啥的 ant design是基于react开发的一个解放ui和前端的工具 它提供了一致的设计方便我们快速开发和减少不必要的设计
  • CocosCreator自动化绑定jsb

    与之前的cocos2dx js自定义js binding不同 这次用的是Cocos2dx里的自动绑定技术 更加的简单 高效 规整以及方便得多 而且之前的手动写文件不能适应更新后的CocosCreator版本的情况 环境配置 JDK NDK
  • 【H∞控制】H无穷控制器的matlab仿真

    1 软件版本 matlab2017b 2 系统概述 PID控制器 PID控制器 比例 积分 微分控制器 由比例单元 P 积分单元 I 和微分单元 D 组成 通过Kp Ki和Kd三个参数的设定 PID控制器主要适用于基本线性和动态特性不随时间
  • Java ffmpeg视频抽帧/转换遇到问题及解决

    Java 视频转换可能会遇到的问题及解决 很多报错都是没有引入 正确的依赖或者版本的问题 javacv版本及ffmpeg版本 1 正确的依赖
  • 狂神说Java--Docker(通俗易懂)

    目录 Docker的入门 Docker的概述 Docker的历史 Docker的用途 Docker的安装 Docker的基本组成 Docker安装 安装 阿里云镜像加速 登录阿里云找到容器服务 找到镜像加速器 配置使用 回顾HelloWor
  • html实现上下三角,css3实现三角形(上下左右)【转载】

    1 向上 kailong width 0 height 0 border right 50pxsolid transparent border left 50pxsolid transparent border bottom 50pxsol
  • Python实现进程管理

    Python实现进程管理 进程管理是操作系统中的重要组成部分 它负责创建 调度和终止进程 以及提供进程间通信和同步的机制 在Python中 我们可以使用多个库和模块来实现进程管理的功能 本文将介绍Python中几个常用的进程管理模块 并提供
  • pytorch打印模型参数

    目录 torchsummary打印 打印模型参数 两种方法结果好像是一样的 打印模型通道均值 获取按通道均值的排序top3索引
  • Sql Server 行转列

    SQL Server 行转列 聚合函数的选择 http www cnblogs com wlsandwho p 4423956 html 摘自网络 PIVOT用于将列值旋转为列名 即行转列 在SQL Server 2000可以用聚合函数配合
  • Token 刷新并发处理解决方案

    对 Token 进行刷新续期 我们要解决并发请求导致重复刷新 Token 的问题 这也是设计刷新 Token 的难点 这里我会分别介绍前端和后端各自的处理方案 后端方案 利用 Redis 缓存 当同时发起多个请求时 第一个接口刷新了 Tok