JavaScript中多AJAX请求并发时的Token刷新问题及解决方案咨询
JavaScript中多AJAX请求并发时的Token刷新问题及解决方案咨询
嗨,这个问题其实在多请求并发的场景里挺常见的,核心矛盾就是多个请求同时触发Token刷新,导致旧的Refresh Token被第一个刷新请求作废后,后面的请求拿着失效的Token去刷新就失败了。下面给你几个能直接落地的前端解决方案,都是实战里常用的:
方案一:用“刷新锁”+请求队列控制
这是最通用的解决思路,核心就是给Token刷新加个“锁”,同一时间只允许一个刷新请求,其他需要刷新的请求先排队等,等新Token拿到后再统一重发。
具体步骤可以这么做:
- 先定义两个全局变量:
isRefreshing(布尔值,标记当前是不是正在刷新Token)、requestQueue(数组,用来存等待重发的请求)。 - 在你的
$.ajaxPrefilter里,当检测到请求返回401时:- 如果
isRefreshing是false,就把它设为true,立刻发起Token刷新请求。 - 如果
isRefreshing已经是true,就把当前这个请求的重试逻辑放进队列里,先暂停执行。
- 如果
- 等Token刷新成功后:
- 更新本地存储的accessToken和refreshToken。
- 遍历队列里的所有请求,用新Token重新发起这些请求,然后清空队列。
- 把
isRefreshing改回false,解锁。
- 如果刷新失败(比如refreshToken也过期了),直接清空队列跳登录页就行。
给你写个jQuery版本的示例代码,你可以参考着改:
// 全局状态变量,用来控制刷新锁和请求队列 let isRefreshing = false; let requestQueue = []; $.ajaxPrefilter(function(options, originalOptions, jqXHR) { // 给每个请求带上当前的accessToken options.headers = options.headers || {}; options.headers['Authorization'] = 'Bearer ' + localStorage.getItem('accessToken'); // 处理401未授权错误 jqXHR.fail(function(xhr) { if (xhr.status === 401) { // 封装原请求的重试逻辑 const retryOriginalRequest = () => $.ajax(originalOptions); if (!isRefreshing) { isRefreshing = true; // 发起Token刷新请求 $.ajax({ url: '/api/refresh-token', method: 'POST', data: { refreshToken: localStorage.getItem('refreshToken') } }).done(function(res) { // 更新本地的Token localStorage.setItem('accessToken', res.accessToken); localStorage.setItem('refreshToken', res.refreshToken); // 重发队列里所有等待的请求 requestQueue.forEach(callback => callback()); requestQueue = []; }).fail(function() { // 刷新失败,跳转到登录页 window.location.href = '/login'; }).always(function() { // 不管成功失败,都解锁 isRefreshing = false; }); } // 将当前请求的重试逻辑加入队列,返回Promise让请求等待 return new Promise(resolve => { requestQueue.push(() => { resolve(retryOriginalRequest()); }); }); } }); });
方案二:提前预判Token过期,主动刷新
如果你的accessToken是JWT格式,你可以提前解析它的过期时间,在Token快过期的时候(比如提前10秒)主动发起一次刷新,而不是等401错误出现再处理。这样能大幅减少并发冲突的概率,不过还是要配合方案一的锁机制,防止多次主动刷新。
举个解析JWT过期时间的小例子:
// 解析JWT的过期时间(转成毫秒) function getTokenExpiryTime(token) { if (!token) return 0; // JWT的payload是中间那段,用base64解码 const payload = JSON.parse(atob(token.split('.')[1])); return payload.exp * 1000; } // 页面加载或每次请求前检查Token是否快过期 const currentToken = localStorage.getItem('accessToken'); const expiryTime = getTokenExpiryTime(currentToken); const now = Date.now(); // 提前10秒刷新,同时确保没有正在进行的刷新请求 if (expiryTime - now < 10000 && !isRefreshing) { // 这里调用方案一里的刷新逻辑就行 }
方案三:后端配合调整Refresh Token逻辑
如果前端改起来觉得麻烦,也可以和后端同事商量,给Refresh Token加个“宽限期”——比如刷新后,旧的Refresh Token不会立刻失效,而是保留30秒的有效期。这样即使后续请求拿着旧Token去刷新,在宽限期内也能成功。不过这个方案需要后端修改逻辑,而且要权衡安全性,宽限期不能太长。
总的来说,方案一是最稳妥的前端独立解决方案,能从根本上解决并发请求的冲突问题,建议你优先试试。如果能结合方案二的主动刷新,用户体验会更好。
备注:内容来源于stack exchange,提问作者Kryten
相关产品推荐
相关产品推荐

