前言

今天我们来聊聊一个看似简单却让无数开发者栽跟头的问题——Token续期

你以为Token续期只是重置时间?90%的系统安全漏洞由此而生!

当用户正在提交重要表单时突然跳转到登录页面,或者系统在高峰期因Token并发刷新而崩溃,这些问题的根源往往在于Token续期策略设计不当

一、Token续期的本质

Token续期不是简单的时间重置,而是安全、用户体验和系统性能的三方博弈

我们先看一个典型事故:

// 错误案例:简单过期的Token检查
public boolean validateToken(String token) {
return JwtUtil.getExpiration(token).after(new Date());
}

这种实现会导致:

  1. 用户操作中断(Token突然过期)
  2. 安全风险(旧Token继续有效)
  3. 并发问题(多个请求同时触发刷新)

Token续期的三大核心问题

  1. 何时续期:提前多久刷新最合理?
  2. 如何续期:单Token还是双Token?有状态还是无状态?
  3. 安全防控:如何防止令牌劫持和并发风暴?

下面我跟大家一起聊聊工作中最常用的5种主流方案,希望对你会有所帮助。

二、单Token方案

2.1 基础实现与致命缺陷

public String refreshToken(String oldToken) {
String username = JwtUtil.parseUsername(oldToken);
return JwtUtil.generateToken(username, 30 * 60); // 直接生成新Token
}

三大致命缺陷

  1. 旧Token在有效期内依然可用(安全黑洞)
  2. 多个请求同时触发刷新会导致多个有效Token并存(并发灾难)
  3. 无法强制下线用户(状态失控)

2.2 黑名单优化方案

代码实现

public String safeRefresh(String oldToken) {
// 旧Token加入黑名单(有效期比Token长5分钟)
redis.setex("blacklist:"+oldToken, "1", 35 * 60); String username = JwtUtil.parseUsername(oldToken);
String newToken = JwtUtil.generateToken(username, 30 * 60);
return newToken;
}

适用场景

  • 内部低安全系统
  • 短期活动页面
  • 快速原型开发

三、双Token方案

3.1 核心架构设计

3.2 安全增强:三验证机制

public TokenPair refreshTokens(String refreshToken) {
// 1. JWT签名验证
if (!JwtUtil.verifySignature(refreshToken)) {
throw new SecurityException("非法令牌");
} // 2. 状态令牌验证
String stateToken = extractStateToken(refreshToken);
if (!redis.exists("state_token:" + stateToken)) {
throw new SecurityException("令牌已失效");
} // 3. 设备绑定验证
String deviceId = getDeviceIdFromRequest();
if (!deviceId.equals(redis.get("bind_device:" + stateToken))) {
throw new SecurityException("设备变更需重新登录");
} return generateNewTokenPair(refreshToken);
}

3.3 并发控制:分布式锁方案

public TokenPair safeRefresh(String refreshToken) {
String lockKey = "refresh_lock:" + refreshToken;
RLock lock = redissonClient.getLock(lockKey); try {
if (lock.tryLock(2, 5, TimeUnit.SECONDS)) {
return doRefresh(refreshToken);
}
throw new BusyException("系统繁忙,请重试");
} finally {
lock.unlock();
}
}

适用场景

  • 金融系统
  • 电商平台
  • 企业级应用

四、自动续期方案

4.1 拦截器+滑动窗口

智能阈值计算

public boolean shouldRenew(Token token) {
long remainTime = token.getExpireTime() - System.currentTimeMillis();
long totalTime = token.getTotalValidTime(); // 双阈值策略:绝对时间(5分钟)和相对时间(30%有效期)
return remainTime <= Math.min(5 * 60 * 1000, 0.3 * totalTime);
}

4.2 Redis缓存续期方案

public void autoRenewToken(String headerToken) {
String cacheKey = "token_cache:" + headerToken;
String cacheToken = redis.get(cacheKey); if (cacheToken == null) throw new TokenExpiredException("令牌已完全过期"); if (JwtUtil.isAboutToExpire(cacheToken)) {
String newToken = generateNewToken();
// 关键:Token更新但缓存Key不变
redis.setex(cacheKey, newToken, 2 * 60 * 60);
response.setHeader("X-New-Token", newToken);
}
}

4.3 Gateway全局过滤器方案

@Component
@Order(-100)
public class TokenRenewFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, Chain chain) {
String token = extractToken(exchange.getRequest()); if (renewService.isRenewRequired(token)) {
String newToken = renewService.renewToken(token);
exchange.getResponse().getHeaders().set("X-New-Token", newToken);
} return chain.filter(exchange);
}
}

适用场景

  • 微服务架构
  • 前后端分离应用
  • 高并发用户系统

五、分布式环境特殊挑战

5.1 多设备会话管理

设备冲突解决方案

public void handleLogin(User user, String deviceType) {
String oldSessionKey = "user_devices:" + user.getId() + ":" + deviceType;
String oldToken = redis.get(oldSessionKey); if (oldToken != null) {
redis.del("token_cache:" + oldToken); // 使旧Token失效
} String newToken = generateToken();
redis.set(oldSessionKey, newToken);
}

5.2 跨服务令牌验证

public boolean validateTokenAcrossServices(String token) {
// 1. 本地快速验证
if (JwtUtil.verifyWithLocalKey(token)) return true; // 2. 查询认证中心
return authCenterClient.validateToken(token);
}

六、五大方案对比

方案 安全性 用户体验 实现复杂度 适用场景 性能影响 典型应用
单Token基础版 ★☆☆☆☆ ★★☆☆☆ ★☆☆☆☆ 内部测试系统 原型开发
单Token+黑名单 ★★☆☆☆ ★★★☆☆ ★★☆☆☆ 低风险Web应用 企业内网
双Token基础版 ★★★☆☆ ★★★★☆ ★★★☆☆ 常规Web/APP 电商平台
双Token+三验证 ★★★★★ ★★★☆☆ ★★★★☆ 金融/支付系统 银行APP
自动续期方案 ★★★★☆ ★★★★★ ★★★★☆ 高用户体验要求系统 中高 SAAS应用

七、方案如何选型?

八、最佳实践与避坑指南

8.1 安全黄金法则

  1. 令牌时效控制

    • Access Token ≤ 30分钟
    • Refresh Token ≤ 7天(需配合刷新次数限制)
  2. 敏感操作二次认证

public void processSensitiveOperation(String token) {
if (isSensitiveOperation()) {
if (!smsVerifyService.verify(getCurrentUser())) {
throw new SecurityException("需要短信验证");
}
}
// 执行操作
}

8.2 性能优化关键

  1. 异步刷新队列

  1. 本地缓存验证
// 使用Caffeine实现本地缓存
LoadingCache<String, Boolean> tokenCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(key -> redis.exists("valid_token:" + key));

8.3 十大避坑指南

  1. 永远不要用长有效期的Access Token
  2. Refresh Token必须是一次性使用的
  3. 客户端必须实现静默更新机制
  4. 分布式环境下必须用RedLock处理并发刷新
  5. 敏感操作必须二次认证
  6. 黑名单有效期需长于Token有效期
  7. 设备变更必须重新认证
  8. 监控Refresh Token的使用频率
  9. 定期轮换签名密钥
  10. 实现令牌撤销接口

好的Token管理系统应该像人体的自主神经系统——平时感受不到它的存在,但在需要时总能及时响应。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

本文收录于我的技术网站:http://www.susan.net.cn

Token续期的5种方案的更多相关文章

  1. SP避免Form重复提交的三种方案

    SP避免Form重复提交的三种方案  1) javascript ,设置一个变量,只允许提交一次.   <script language="javascript">  ...

  2. Struts1.X与Spring集成——第一种方案

    spring+struts(第一种方案) 集成原理:在Action中取得BeanFactory,通过BeanFactory取得业务逻辑对象,调用业务逻辑方法. 一,新建一个项目Spring_Strut ...

  3. http无状态和鉴权解决四种方案

    http协议本身是无状态的,但是在实际的web开发中常有一些操作需要有状态.比如想要访问一些私人访问权限的文章,或者这种操作需要明确当前用户身份. 显然,最简单的方案就是每次都发送账户和密码,但是这样 ...

  4. 如何快速排查发现redis的bigkey?4种方案一次性给到你!

    本篇文章将以redis的bigkey为主题进行技术展开,通过从认识redis的高性能,bigkey的危害.存在原因.4种解决方案,到模拟实战演练的介绍方式,来跟大家一起认识.探讨和学习redis. 先 ...

  5. 【Win 10 应用开发】文件读写的三种方案

    本文老周就跟伙伴们探讨一下关于文件读写的方法.总得来说嘛,有三种方案可以用,而且每种方案都各有特色,也说不上哪种较好.反正你得记住老祖宗留给我们的大智慧——事无定法,灵活运用者为上. OK,咱们开始吧 ...

  6. 集合框架,ArrayList和Vector的区别,让arrayList线程安全的几种方案

    boolean add(E e) 将指定的元素添加到此列表的尾部. void add(int index, E element) 将指定的元素插入此列表中的指定位置. boolean addAll(C ...

  7. javascript 网络是否连接的几种方案

    js   网络是否连接的几种方案 1.通过html5的新属性: window.onload = function () {            var isOnLine = navigator.on ...

  8. javascript把IP地址转为数值几种方案,来挑战一下效率吧

    先看看什么是IP地址: IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节).IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~25 ...

  9. [转] Asp.Net 导出 Excel 数据的9种方案

    湛刚 de BLOG 原文地址 Asp.Net 导出 Excel 数据的9种方案 简介 Excel 的强大之处在于它不仅仅只能打开Excel格式的文档,它还能打开CSV格式.Tab格式.website ...

  10. 浅谈实现placeholder效果的几种方案

    placeholder是html5<input>的一个属性,它提供可描述输入字段预期值的提示信息(hint), 该提示会在输入字段为空时显示.高端浏览器支持此属性(ie10/11在获得焦点 ...

随机推荐

  1. 一文搞懂K8s中的RBAC认证授权

    概述 官方文档: https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/authorization/ https://kubern ...

  2. PD项目管理工具实战指南:产品研发流程优化的数字化利器(2025深度选型推荐)

    在研发项目管理领域,越来越多企业正从传统"瀑布式开发"转向 集成产品开发(IPD)模式.这一方法论不仅强调跨部门协同.流程分阶段控制,更对工具系统提出了更高要求:要能穿透组织架构. ...

  3. 使用 ftrace 跟踪内核丢包问题定位的实践

    本文分享自天翼云开发者社区<使用 ftrace 跟踪内核丢包问题定位的实践>,作者:f****n 数据包的丢失可能会导致性能下降或服务中断.为了诊断内核中是否有丢包问题,我们可以使用 ft ...

  4. openxml文书工具 Aspose 工具 word to pdf

    aspose模板生成(文书工具) 动态数据 ${info} ${list.id} ${list.name} ${list.address} ${list.date} ${list.danwei} ${ ...

  5. 保姆级教程!玩转 ChunJun 详细指南

    ChunJun是一款稳定.易用.高效.批流一体的数据集成框架,⽀持海量数据的同步与计算.ChunJun 既可以采集静态的数据,比如 MySQL,HDFS 等,也可以采集实时变化的数据,比如 binlo ...

  6. Java源码分析系列笔记-9.CountDownLatch

    目录 1. 是什么 2. 如何使用 2.1. CountDownLatch VS CyclicBarrier 3. uml 4. 构造方法 4.1. Sync[AQS子类] 5. countDown方 ...

  7. FastAPI如何玩转安全防护,让黑客望而却步?

    扫描二维码 关注或者微信搜一搜:编程智域 前端至全栈交流与成长 发现1000+提升效率与开发的AI工具和实用程序:https://tools.cmdragon.cn/ 1. FastAPI安全基础架构 ...

  8. 🛡️ Nginx 配置 HTTPS 完整指南

    一.准备工作 域名已备案(国内服务器) SSL/TLS 证书和私钥文件 通常包括: your_domain.crt:服务器证书 your_domain.key:私钥文件 chain.crt 或 ca. ...

  9. CF2064E Mycraft Sand Sort 题解

    CF2064E Mycraft Sand Sort 第一次一眼秒了一道 E,但是被人均六分钟 C 题硬控一小时,未能写完,遗憾离场,特此纪念. 考虑第一列,无论排列 \(p'\) 是什么样子,第一列一 ...

  10. java compareTo 与 equals 区别

    简介 要实现compareTo函数需要实现接口Comparable这个接口 然后这个接口中只有compareTo函数实现一下就可以用Collections.sort等方法. equals 如果不重写, ...