You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于OAuth2的前后端分离Web应用刷新Access Token方案问询

安全高效刷新Access Token的方案(前后端分离OAuth2场景)

Hey,针对你这个前后端分离的OAuth2架构,我来分享一套经过实践验证的安全高效的刷新流程,刚好匹配你后端存Refresh Token、前端存Access Token的设定:

一、核心刷新流程

这是最基础的链路逻辑,先把流程跑通:

  • 前端每次发起业务请求时,在请求头里携带 Authorization: Bearer ${accessToken}
  • 后端校验Access Token:
    • 令牌有效就正常返回业务数据
    • 如果返回401 Unauthorized,且明确标记是Access Token过期(一定要和其他401场景区分开,比如令牌伪造、权限不足),前端触发刷新流程
  • 前端调用后端专门的刷新令牌接口(比如POST /oauth/token),请求体携带:
    • grant_type=refresh_token
    • refresh_token=${本地存储的Refresh Token}
    • (如果你的Client是保密型,可能还要加client_id和client_secret,但前端作为公开Client的话,后端要做好来源校验)
  • 后端校验Refresh Token:
    • 确认有效且未过期,就签发新的Access Token(建议同时签发新的Refresh Token,进一步提升安全性)
    • 更新后端存储的Refresh Token(如果发了新的),作废旧令牌
    • 将新的Access Token(和新Refresh Token)返回给前端
  • 前端更新本地存储的令牌,重新发起之前失败的业务请求

二、优化体验与效率的细节

1. 提前刷新,实现无感体验

别等Access Token完全过期才动手!前端可以从JWT的exp字段解析出过期时间,每次请求前检查剩余有效期,如果小于设定的阈值(比如5分钟),就提前调用刷新接口。用户完全不会感知到这个过程,体验更流畅。

2. 解决并发请求的竞态问题

如果同时有多个请求因为令牌过期返回401,别让每个请求都触发刷新——这会导致重复调用刷新接口,浪费资源。可以用全局Promise锁来处理:

  • 第一次触发刷新时,把刷新请求的Promise存在全局变量里
  • 后续所有失败的请求都等待这个Promise完成,拿到新令牌后再统一重发
  • 刷新完成后清空全局变量,避免影响后续请求

给你一段前端伪代码参考:

// 全局变量存储刷新中的Promise
let refreshInProgress = null;

async function fetchWithAuth(url, options = {}) {
  const accessToken = localStorage.getItem('accessToken');
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${accessToken}`
      }
    });

    // 判定为Access Token过期的401(后端需自定义响应头标记)
    if (response.status === 401 && response.headers.get('X-Token-Expired') === 'true') {
      if (!refreshInProgress) {
        refreshInProgress = refreshAccessToken();
      }
      // 等待刷新完成
      const newTokens = await refreshInProgress;
      refreshInProgress = null;
      // 更新本地令牌
      localStorage.setItem('accessToken', newTokens.accessToken);
      localStorage.setItem('refreshToken', newTokens.refreshToken);
      // 重发原请求
      return fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          'Authorization': `Bearer ${newTokens.accessToken}`
        }
      });
    }
    return response;
  } catch (err) {
    // 异常时清空全局锁
    refreshInProgress = null;
    throw err;
  }
}

async function refreshAccessToken() {
  const refreshToken = localStorage.getItem('refreshToken');
  const response = await fetch('/oauth/token', {
    method: 'POST',
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken
    })
  });

  if (!response.ok) {
    // 刷新失败,比如Refresh Token过期,直接跳登录
    window.location.href = '/login';
    throw new Error('Refresh token invalid or expired');
  }
  return response.json();
}

三、安全层面的关键注意事项

安全是OAuth2的核心,这些细节不能少:

  • Refresh Token的存储:
    • 别把Refresh Token存在localStorage!建议存在HttpOnly、Secure、SameSite=Strict的Cookie里(如果前后端同域或配置了跨域Cookie),能有效避免XSS攻击窃取令牌
    • 如果跨域场景没法用Cookie,一定要对Refresh Token加密后再存在前端存储,同时页面要做好XSS防护
  • 后端校验逻辑:
    • 除了校验Refresh Token的签名和过期时间,还要检查它是否在后端的存储列表中(防止篡改或复用)
    • 每次刷新后,一定要签发新的Refresh Token并作废旧的,减少令牌泄露后的风险
    • 给Refresh Token设置合理的过期时间,并且支持用户登出时手动注销令牌
  • 接口防护:
    • 刷新令牌接口要做限流,防止暴力破解
    • 前端作为公开Client,后端要验证请求的Origin/Referer,或者结合其他方式校验请求合法性

四、异常处理预案

  • 如果Refresh Token也过期了,前端直接跳转到登录页面,引导用户重新认证
  • 如果刷新接口返回401(比如Refresh Token被篡改、已注销),同样触发登录流程
  • 捕获刷新过程中的网络异常,给用户友好提示(比如“登录状态失效,请重新登录”),别让用户摸不着头脑

内容的提问来源于stack exchange,提问作者patak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:57:27