前后端token自动续期方案

在前后端分离架构中,Token 是实现身份认证的核心机制,但 Token 过期会导致用户操作中断,影响体验。因此,自动续期方案是保障用户持续访问的关键。以下是几种主流的 Token 自动续期方案,包含原理、实现细节及安全性考量。

一、刷新 Token(Refresh Token)机制

这是目前最成熟、应用最广泛的方案,核心是通过“双 Token 体系”实现续期,兼顾安全性和用户体验。

1. 核心原理
  • Access Token(访问令牌):短期有效(如 15 分钟),用于接口访问,权限范围小,泄露风险低。
  • Refresh Token(刷新令牌):长期有效(如 7 天),仅用于获取新的 Access Token,权限范围仅为“续期”,泄露影响可控。

当 Access Token 过期时,前端用未过期的 Refresh Token 向后端换取新的 Access Token,避免用户重新登录。

2. 前后端实现流程
前端实现(以 Axios 为例)
  • 存储 Token:Access Token 存在内存或 httpOnly Cookie(推荐,防 XSS);Refresh Token 存在 httpOnly Cookie(避免前端 JS 读取,增强安全性)。

  • 请求拦截器:每次请求在 Header 中携带 Access Token(如 Authorization: Bearer {accessToken})。

  • 响应拦截器:处理 401 错误(Access Token 过期):

    • 若存在 Refresh Token,调用后端“刷新接口”获取新 Access Token。
    • 刷新成功后,更新本地 Access Token,并重试原请求。
    • 若 Refresh Token 也过期(刷新接口返回 401),则清除本地 Token,跳转至登录页。
    // 响应拦截器示例
    axios.interceptors.response.use(
      (response) => response,
      async (error) => {
        const originalRequest = error.config;
        // 若为401且未尝试过刷新
        if (error.response.status === 401 && !originalRequest._retry) {
          originalRequest._retry = true; // 标记已尝试刷新,避免重复请求
          try {
            // 调用刷新接口(Refresh Token 通过 Cookie 自动携带)
            const { data } = await axios.post('/api/refresh-token');
            // 更新本地 Access Token
            localStorage.setItem('accessToken', data.accessToken);
            // 重试原请求(携带新 Token)
            originalRequest.headers['Authorization'] = `Bearer ${data.accessToken}`;
            return axios(originalRequest);
          } catch (refreshError) {
            // 刷新失败(Refresh Token 过期),跳转登录
            window.location.href = '/login';
            return Promise.reject(refreshError);
          }
        }
        return Promise.reject(error);
      }
    );
    
  • 并发请求处理:若多个请求同时触发 401,需避免重复刷新 Token。可通过“锁机制”实现:刷新过程中阻塞其他请求,等待新 Token 生成后统一重试。

后端实现
  • 生成 Token:登录成功时,同时生成 Access Token(短期)和 Refresh Token(长期),返回给前端(Access Token 可放响应体,Refresh Token 放 httpOnly Cookie)。

  • 验证 Access Token:接口请求时,验证 Access Token 的签名和有效期,无效则返回 401。

  • 刷新接口实现

    • 接收 Refresh Token(从 Cookie 或请求体获取),验证其有效性(是否存在于数据库、未过期、未被吊销)。
    • 验证通过后,生成新的 Access Token(和新的 Refresh Token,可选,增强安全性)。
    • 返回新 Token 给前端。
    // 后端刷新接口示例(伪代码)
    @PostMapping("/api/refresh-token")
    public Result refreshToken(HttpServletRequest request) {
      // 从 Cookie 获取 Refresh Token
      String refreshToken = CookieUtils.getCookieValue(request, "refreshToken");
      if (refreshToken == null) {
        return Result.fail(401, "刷新令牌不存在");
      }
      // 验证 Refresh Token(查库+验签)
      RefreshTokenEntity entity = refreshTokenMapper.selectByToken(refreshToken);
      if (entity == null || entity.getExpireTime().before(new Date())) {
        return Result.fail(401, "刷新令牌已过期");
      }
      // 生成新 Access Token 和 Refresh Token
      String newAccessToken = JwtUtils.generateAccessToken(entity.getUserId());
      String newRefreshToken = JwtUtils.generateRefreshToken(entity.getUserId());
      // 更新数据库中的 Refresh Token(旧的标记失效,存储新的)
      refreshTokenMapper.invalidateOldToken(entity.getId());
      refreshTokenMapper.insert(new RefreshTokenEntity(newRefreshToken, entity.getUserId()));
      // 返回新 Token(Access Token 放响应体,Refresh Token 放 httpOnly Cookie)
      return Result.success(Map.of(
        "accessToken", newAccessToken,
        "refreshToken", newRefreshToken // 可选,通过 Cookie 返回更安全
      ));
    }
    
3. 优缺点
  • 优点:安全性高(Access Token 短期有效,泄露影响小;Refresh Token 可吊销);用户体验好(无需频繁登录)。
  • 缺点:实现较复杂(需维护双 Token 逻辑);需后端存储 Refresh Token(增加存储成本)。

二、滑动窗口续期(无 Refresh Token)

适用于对安全性要求中等、希望简化实现的场景,核心是“Token 快过期时自动延长有效期”。

1. 核心原理
  • 仅使用一个 Access Token,有效期设为中等(如 1 小时)。
  • 当 Token 剩余有效期小于阈值(如 10 分钟)时,前端请求接口时,后端自动生成新 Token 并返回,前端更新本地 Token。
  • 本质是“用持续活跃的请求延长 Token 有效期”,类似“用户活跃则会话不失效”。
2. 前后端实现流程
前端实现
  • 存储 Token 并在请求头携带。

  • 每次收到响应后,检查是否包含新 Token(如响应头 X-New-Token),若有则更新本地存储。

    // 响应拦截器示例
    axios.interceptors.response.use(
      (response) => {
        // 若响应头有新 Token,更新本地存储
        const newToken = response.headers['x-new-token'];
        if (newToken) {
          localStorage.setItem('accessToken', newToken); // 建议用 Cookie
        }
        return response;
      },
      (error) => {
        // 若 Token 已过期(401),直接跳转登录
        if (error.response?.status === 401) {
          window.location.href = '/login';
        }
        return Promise.reject(error);
      }
    );
    
后端实现
  • Token 中包含过期时间(如 exp 字段)。

  • 每次验证 Token 时,计算剩余有效期:若剩余时间 < 阈值(如 10 分钟),则生成新 Token,放在响应头(如 X-New-Token)返回。

    // 验证 Token 并判断是否续期(伪代码)
    public String validateAndRenewToken(String token) {
      // 验证 Token 签名和有效性
      Claims claims = JwtUtils.parseToken(token);
      if (claims == null) {
        throw new TokenInvalidException();
      }
      // 计算剩余有效期(当前时间到 exp 的差值)
      long expireTime = claims.getExpiration().getTime();
      long remainingTime = expireTime - System.currentTimeMillis();
      // 若剩余时间 < 10 分钟,生成新 Token
      if (remainingTime < 10 * 60 * 1000) {
        String newToken = JwtUtils.generateAccessToken(claims.getSubject()); // 用原用户 ID 生成新 Token
        return newToken;
      }
      return null; // 无需续期
    }
    
3. 优缺点
  • 优点:实现简单(无需维护 Refresh Token);用户体验流畅(无感续期)。
  • 缺点:安全性较低(Token 有效期较长,泄露后风险更高);无法处理“用户长时间不操作”的场景(Token 会直接过期)。

三、主动预检续期

适用于对请求成功率要求高的场景,前端主动检测 Token 有效期,提前续期。

1. 核心原理
  • 前端每次发送请求前,检查 Token 剩余有效期:
    • 若剩余时间 > 阈值(如 5 分钟):直接发送请求。
    • 若剩余时间 ≤ 阈值:先发送续期请求获取新 Token,再发送原请求。
  • 避免了“请求因 Token 过期失败后重试”的情况,更主动。
2. 前后端实现流程
前端实现(请求拦截器中处理)
axios.interceptors.request.use(async (config) => {
  const token = localStorage.getItem('accessToken');
  if (!token) {
    window.location.href = '/login';
    return Promise.reject(new Error('未登录'));
  }
  // 解析 Token 中的过期时间(需前端解析 JWT,或后端返回过期时间)
  const payload = JSON.parse(atob(token.split('.')[1]));
  const expireTime = payload.exp * 1000; // JWT 的 exp 是秒级时间戳
  const remainingTime = expireTime - Date.now();
  
  // 若剩余时间 ≤ 5 分钟,先续期
  if (remainingTime <= 5 * 60 * 1000) {
    try {
      const { data } = await axios.post('/api/renew-token', { token });
      const newToken = data.newToken;
      localStorage.setItem('accessToken', newToken);
      config.headers['Authorization'] = `Bearer ${newToken}`;
    } catch (error) {
      // 续期失败,跳转登录
      window.location.href = '/login';
      return Promise.reject(error);
    }
  } else {
    config.headers['Authorization'] = `Bearer ${token}`;
  }
  return config;
});
后端实现
  • 提供专门的续期接口(/api/renew-token),接收旧 Token,验证有效性后返回新 Token。
  • 逻辑与滑动窗口的后端类似,但触发时机由前端主动控制。
3. 优缺点
  • 优点:避免请求失败重试,成功率高;主动处理续期,流程更可控。
  • 缺点:增加了一次预检请求(性能损耗小);需前端解析 Token 过期时间(JWT 可解析,非 JWT 需后端额外返回)。

四、安全性与最佳实践

  1. Token 存储

    • 优先用 httpOnly + Secure + SameSite Cookie 存储(防 XSS、CSRF 攻击)。
    • 避免用 localStorage/sessionStorage(易被 XSS 窃取)。
  2. 过期时间设置

    • Access Token:短期(15 分钟 ~ 1 小时),减少泄露影响。
    • Refresh Token:中期(7 ~ 30 天),兼顾体验与安全;可设置“绝对过期时间”(如 30 天),强制用户定期登录。
  3. Refresh Token 轮换

    • 每次用 Refresh Token 换取新 Access Token 时,同步生成新的 Refresh Token(旧的立即失效),防止 Refresh Token 被复用。
  4. HTTPS 传输:所有 Token 必须通过 HTTPS 传输,防止中间人攻击。

  5. 并发请求处理

    • 用“锁机制”(如 Promise 等待)处理多个请求同时触发续期的场景,避免重复调用刷新接口。
  6. Token 吊销

    • 后端需支持主动吊销 Token(如用户登出、修改密码时),可通过“黑名单”(Redis 存储已吊销 Token)实现快速验证。

总结

  • 追求安全性:优先选 Refresh Token 机制(双 Token 体系)。
  • 追求简单实现:选 滑动窗口续期(无 Refresh Token)。
  • 追求请求成功率:选 主动预检续期(前端提前处理)。

实际场景中,可结合业务需求(如用户活跃度、安全等级)选择方案,核心是在“用户体验”与“安全性”之间找到平衡。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值