当前位置: 首页 > article >正文

JWT 过期后 自动刷新方案

JWT(JSON Web Token)广泛应用于现代 Web 开发中的认证与授权,它以无状态、灵活和高效的特点深受开发者欢迎。然而,JWT 的一个核心问题是 Token 过期后如何处理。本文将总结常见的解决方案,分析其优缺点,并帮助开发者选择适合自己项目的方案。


JWT 过期问题的挑战
  1. 安全性:如果 Token 长时间有效,泄露后可能导致严重的安全问题;短时间有效则需要频繁刷新。
  2. 无状态性:JWT 通常是无状态的,如何在不依赖后端存储的情况下管理过期 Token 成为难点。
  3. 刷新机制:如何设计高效、安全的 Token 刷新机制,避免用户频繁登录体验不佳。

解决方案

方案一:双 Token 模式(Access Token + Refresh Token)
设计思路
  • 使用两个 Token:
    1. Access Token:短生命周期(如 15 分钟)。
    2. Refresh Token:长生命周期(如 7 天)。
  • 客户端使用 Access Token 请求资源;当 Access Token 过期时,使用 Refresh Token 获取新的 Access Token 和 Refresh Token。
优点
  • 高安全性:Access Token 即使泄露,时间窗口较短,风险可控。
  • 客户端和服务器可以协同管理 Refresh Token,有效防止重复使用攻击。
缺点
  • 客户端实现复杂度较高,需要管理两个 Token。
  • 需要额外的接口和逻辑处理 Refresh Token。
适用场景
  • 对安全性要求较高的系统,如金融、支付平台。
核心代码

后端接口验证 Refresh Token 并生成新 Token:

@PostMapping("/refresh-token")
public ResponseEntity<?> refreshToken(@RequestBody Map<String, String> tokens) {
    String refreshToken = tokens.get("refreshToken");

    if (!jwtUtil.checkToken(refreshToken)) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid Refresh Token");
    }

    Map<String, String> claims = jwtUtil.decode(refreshToken);
    String newAccessToken = jwtUtil.generateJwtToken(claims.get("username"), claims.get("userId"));
    String newRefreshToken = jwtUtil.generateRefreshToken(claims.get("username"), claims.get("userId"));

    return ResponseEntity.ok(Map.of("accessToken", newAccessToken, "refreshToken", newRefreshToken));
}

方案二:固定 Session ID + Token
设计思路
  • 后端生成唯一的 Session ID 并与 Token 关联,将会话信息(如 Token 状态、用户信息)存储到 Redis 或数据库。
  • 客户端请求时,携带 Session ID 和 Token,后端验证会话状态和 Token。
优点
  • 可灵活控制会话状态,支持主动使某些 Session 失效。
  • 后端可以精细化管理 Token 状态,无需频繁解析 JWT 签名。
缺点
  • 依赖 Redis 或数据库存储会话信息,增加系统复杂度。
  • 不完全无状态,分布式部署时需要额外处理会话同步。
适用场景
  • 用户登录频繁、对会话状态要求高的系统。
核心代码
// 从 Redis 获取会话信息并验证 Token
SessionInfo sessionInfo = redisUtil.get("session_" + sessionId, SessionInfo.class);
if (sessionInfo == null || !sessionInfo.getToken().equals(token)) {
    return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Session Invalid");
}

// Token 过期时刷新
if (jwtUtil.isTokenExpired(token)) {
    String newToken = jwtUtil.generateJwtToken(sessionInfo.getUsername(), sessionInfo.getUserId());
    sessionInfo.setToken(newToken);
    redisUtil.set("session_" + sessionId, sessionInfo);
    return ResponseEntity.ok(Map.of("newToken", newToken));
}

方案三:Token 无状态 + 客户端定时刷新
设计思路
  • Token 完全无状态,仅存储签名信息和过期时间。
  • 客户端定期检查 Token 的有效期,并在即将过期时主动请求刷新。
优点
  • 无需后端存储任何会话信息,完全无状态。
  • 简单高效,适合分布式系统。
缺点
  • 客户端需要管理 Token 刷新逻辑,增加复杂度。
  • 无法主动失效某个用户会话(如强制下线)。
适用场景
  • 轻量级或低安全需求的系统,如小型工具或内部系统。
核心代码

客户端定时检查并刷新:

function checkTokenExpiry() {
    const token = localStorage.getItem('accessToken');
    const payload = JSON.parse(atob(token.split('.')[1]));
    const expiryTime = payload.exp * 1000;

    if (Date.now() > expiryTime - 5 * 60 * 1000) { // 提前 5 分钟刷新
        refreshToken();
    }
}

function refreshToken() {
    const refreshToken = localStorage.getItem('refreshToken');
    fetch('/refresh-token', {
        method: 'POST',
        body: JSON.stringify({ refreshToken }),
        headers: { 'Content-Type': 'application/json' }
    })
    .then(response => response.json())
    .then(data => {
        localStorage.setItem('accessToken', data.accessToken);
        localStorage.setItem('refreshToken', data.refreshToken);
    });
}

方案四:Hybrid Token(混合 Token)
设计思路
  • 将部分会话信息存储到 Token 中,而状态信息(如是否过期)存储在 Redis。
  • Token 验证时仅检查无状态部分,必要时从 Redis 获取状态信息。
优点
  • 兼顾无状态性和灵活性。
  • 后端可动态管理会话状态,同时减少频繁访问存储。
缺点
  • 实现较复杂,适合对性能要求较高的场景。
适用场景
  • 高并发、大规模分布式系统,如社交平台、内容分发系统。

方案对比

方案优点缺点适用场景
双 Token 模式高安全性;支持长生命周期管理增加客户端复杂度金融、支付等高安全系统
Session ID + Token灵活控制会话状态,支持主动失效依赖 Redis 或数据库存储登录频繁的系统
无状态 Token完全无状态,简单高效客户端复杂度高,无法主动失效内部工具、轻量级系统
Hybrid Token兼顾性能和灵活性实现复杂高并发分布式系统

结语

不同的方案适用于不同的场景,开发者在选择方案时需要考虑:

  1. 安全性:是否需要强安全性保障,如支持主动失效。
  2. 性能:是否需要减少后端存储依赖或高效处理。
  3. 复杂度:是否愿意增加客户端和后端的实现复杂性。

在实际开发中,可以结合业务需求和技术条件选择最适合的方案,甚至进行多方案组合,实现性能与安全的平衡。


http://www.kler.cn/a/397656.html

相关文章:

  • 传奇996_24——变量lua
  • 不用来回切换,一个界面管理多个微信
  • 手机ip地址异常怎么解决
  • 使用Redis的一些经验总结
  • DVWA靶场通关——SQL Injection篇
  • Tensorflow基本概念
  • git/dvc笔记
  • ElasticSearch学习笔记一:简单使用
  • 项目启动运行npm run dev报错digital envelope routines::unsupported at new Hash
  • 用 Python 从零开始创建神经网络(五):损失函数(Loss Functions)计算网络误差
  • 【030】基于51单片机甲醛检测报警器【Proteus仿真+Keil程序+报告+原理图】
  • 动态网页爬取 —— ajax 与 selenium
  • Scratch 015生日贺卡(下)
  • 技术理论||01无人机倾斜摄影原理
  • ERROR TypeError: AutoImport is not a function
  • kafka中是如何快速定位到一个offset的
  • 计算机的错误计算(一百五十六)
  • Git设置用户名及邮箱
  • 接口文档的编写
  • 深入解析 CentOS 7 上 MySQL 8.0 的最佳实践20241112
  • 新版Servlet3.0~5.0和旧版配置的区别
  • 算法练习:438. 找到字符串中所有字母异位词
  • 【Rust中的项目管理】
  • vue之axios根据某个接口创建实例,并设置headers和超时时间,捕捉异常
  • MySQL8 安装教程
  • 【网络安全面经】技术性问题