token无感刷新+处理并发的后端方案

问题描述:

当用户通过登陆后进入一个web网站,会把token保存到localStorage。假设token过期时间30min。

那么当用户在网站快乐地玩耍了30min后,这时进行了一次提交表单,它会被重定向到登陆页面。

作为用户:我表单填了这么久,点击提交时让我重新登陆?我的体验很不好。

jwt的好处和局限

jwt的好处是:服务器无需存储这些登陆的状态

而它的局限是:服务器无法主动地通知用户"你的token过期了,我重新给你一个"。

如何解决

方案一:双token

在登陆时我们会生成俩个token

  • token:表示正常情况下登陆凭证
  • refresh-token:表示需要刷新情况下登陆凭证

假设前者(token)设置过期时间为30min,后者为1天。

流程

  1. time=0min,用户成功登陆,后端返回俩个token(token和refresh-token),前端把它俩保存到localStorage
  2. time=10min,用户进行了一次查询,前端将俩个token都发给后端,后端检验token,有效,返回结果,用户很开心!
  3. time=35min,用户提交了表单,前端还是将俩个token发给后端;后端检验token,过期;检验refresh-token,有效,后端认为这是要刷新token,生成新的token和refresh-token,成功返回数据和双token。前端把数据渲染,把双token保存。

token正常过期的情况:

当然,token可以正常过期,如果在检验时refresh-token也过期,那就说明正常过期

假设time=0min时用户登陆,time=2天时用户提交了表单,那么后端认为这是正常过期,用户需要重新登陆。

流程图如下:

token刷新并发问题

现在很多登陆是可以多端的,当多端并发都去尝试刷新token时,会导致token被重复刷新

方案二:刷新时用分布式锁

可以引入redis缓存中间件,使用缓存加速并处理并发刷新问题。

将双token生成后存入redis。当需要刷新token时,采用double-check+获取关于refresh-token的分布式锁来实现。

伪代码如下:

// 验证和刷新Token的方法

public String validateAndRefreshToken(String userId, String accessToken, String refreshToken) {

String storedAccessToken = redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId);

String storedRefreshToken = redisTemplate.opsForValue().get(REFRESH_TOKEN_PREFIX + userId);

// 检查Access Token是否有效

if (storedAccessToken != null && storedAccessToken.equals(accessToken)) {

return accessToken; // 直接返回原始的Access Token

}

// Access Token无效,检查Refresh Token

if (storedRefreshToken != null && storedRefreshToken.equals(refreshToken)) {

// 使用双层检查和分布式锁控制高并发刷新

String lockKey = "refresh_lock:" + userId;

boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS);

if (lockAcquired) {

try {

// 再次双层检查(避免重复刷新)

storedAccessToken = redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId);

if (storedAccessToken == null) {

// 生成新的Access Token

String newAccessToken = generateToken();

redisTemplate.opsForValue().set(ACCESS_TOKEN_PREFIX + userId, newAccessToken, 15, TimeUnit.MINUTES);

return newAccessToken;

} else {

return storedAccessToken; // 直接返回已刷新过的Token

}

} finally {

redisTemplate.delete(lockKey); // 释放锁

}

} else {

// 未获取到锁,等待其他线程刷新完成,再次获取已刷新的Access Token

return redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId);

}

}

// 若Refresh Token也无效,返回null或抛出异常,需重新登录

throw new TokenInvalidException("Access and Refresh Token both expired.");

}

方案三:过渡时间(没看懂)

相关推荐
我命由我123452 小时前
Kotlin 数据容器 - List(List 概述、创建 List、List 核心特性、List 元素访问、List 遍历)
java·开发语言·jvm·windows·java-ee·kotlin·list
白-胖-子4 小时前
深入剖析大模型在文本生成式 AI 产品架构中的核心地位
人工智能·架构
武子康4 小时前
Java-80 深入浅出 RPC Dubbo 动态服务降级:从雪崩防护到配置中心秒级生效
java·分布式·后端·spring·微服务·rpc·dubbo
YuTaoShao7 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
源码_V_saaskw7 小时前
JAVA图文短视频交友+自营商城系统源码支持小程序+Android+IOS+H5
java·微信小程序·小程序·uni-app·音视频·交友
超浪的晨8 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
Pomelo_刘金8 小时前
用 DDD 把「闹钟」需求一点点捏出来
架构·rust·领域驱动设计
Pomelo_刘金8 小时前
Clean Architecture 整洁架构:借一只闹钟讲明白「整洁架构」的来龙去脉
后端·架构·rust
双力臂4048 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
碳酸的唐8 小时前
Inception网络架构:深度学习视觉模型的里程碑
网络·深度学习·架构