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

Go Gin+Next.js认证系统中Access Token与Refresh Token处理咨询

Access Token与Refresh Token处理方案(Gin+Next.js)

一、何时触发Token刷新?两种方案结合更稳妥

  • 被动触发(401拦截):这是最通用的实现方式,不需要前端维护复杂的定时逻辑。但要注意在后端区分「Access Token过期」和「Token无效/权限不足」的401场景,避免无效的刷新请求。
  • 主动触发(定时预刷新):在Access Token即将过期前(比如设置为过期时间的80%节点),主动发起刷新请求。适合对用户体验要求高的场景,避免请求失败后再重试的卡顿感。

建议两种方案结合:前端优先做预刷新兜底,若预刷新失败(比如网络问题),再通过401拦截逻辑重试。

二、基于数据库存储Refresh Token的刷新流程

后端(Gin)实现步骤

  1. 定义刷新接口:接口仅接收Refresh Token(建议通过HttpOnly+Secure的Cookie传递),返回新的Access Token。
  2. 验证Refresh Token合法性:
    • 从请求中取出Refresh Token,查询数据库对应的记录(需关联用户ID、过期时间、是否被吊销)。
    • 若数据库中存在该Token且未过期、未被吊销,生成新的短有效期Access Token(比如15分钟)。
    • 可选:生成新的Refresh Token,替换数据库中的旧记录(滚动刷新,提升安全性)。
  3. 返回新Token:将新的Access Token存入Cookie,返回成功响应。

示例代码(Gin):

import (
    "net/http"
    "time"
    "github.com/gin-gonic/gin"
    "your-project/models"
    "your-project/utils"
)

// 刷新Token接口
func RefreshTokenHandler(c *gin.Context) {
    // 从Cookie获取Refresh Token
    refreshToken, err := c.Cookie("refresh_token")
    if err != nil {
        c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"msg": "缺少有效Refresh Token"})
        return
    }

    // 查询数据库中的Refresh Token记录
    var tokenRecord models.RefreshToken
    if err := models.DB.Where("token = ? AND expires_at > ?", refreshToken, time.Now()).First(&tokenRecord).Error; err != nil {
        c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"msg": "Refresh Token无效或已过期"})
        return
    }

    // 生成新的Access Token
    accessToken, err := utils.GenerateAccessToken(tokenRecord.UserID)
    if err != nil {
        c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"msg": "生成Access Token失败"})
        return
    }

    // 滚动刷新Refresh Token(可选)
    newRefreshToken, err := utils.GenerateRefreshToken(tokenRecord.UserID)
    if err != nil {
        c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"msg": "生成Refresh Token失败"})
        return
    }
    // 更新数据库记录
    tokenRecord.Token = newRefreshToken
    tokenRecord.ExpiresAt = time.Now().Add(7 * 24 * time.Hour)
    models.DB.Save(&tokenRecord)

    // 设置新的Access Token到Cookie
    c.SetCookie("access_token", accessToken, 15*60, "/", "your-domain.com", true, true)
    // 若滚动刷新,同步更新Refresh Token的Cookie
    c.SetCookie("refresh_token", newRefreshToken, 7*24*3600, "/", "your-domain.com", true, true)

    c.JSON(http.StatusOK, gin.H{"msg": "Token刷新成功"})
}

前端(Next.js)实现步骤

  1. 封装请求拦截器:用axios或Next.js的fetch封装请求,每次请求自动携带Access Token(从Cookie中读取)。
  2. 响应拦截器处理401:捕获401响应,判断为Access Token过期后,调用刷新接口,成功后重试原请求。
  3. 定时预刷新逻辑:页面加载时解析Access Token的过期时间,设置定时器在过期前5分钟发起刷新请求。

示例代码(Next.js + axios):

import axios from 'axios';
import { useEffect } from 'react';

const api = axios.create({
  baseURL: '/api',
  withCredentials: true // 允许携带Cookie
});

// 响应拦截器
api.interceptors.response.use(
  res => res,
  async err => {
    const originalReq = err.config;
    // 判断是否为Access Token过期的401,且未重试过
    if (err.response.status === 401 && !originalReq._retry) {
      originalReq._retry = true;
      try {
        // 调用刷新接口
        await api.post('/refresh-token');
        // 重试原请求
        return api(originalReq);
      } catch (refreshErr) {
        // 刷新失败,跳转到登录页
        window.location.href = '/login';
        return Promise.reject(refreshErr);
      }
    }
    return Promise.reject(err);
  }
);

// 定时预刷新函数
const setupRefreshTimer = () => {
  const accessToken = document.cookie.split('; ').find(row => row.startsWith('access_token='))?.split('=')[1];
  if (!accessToken) return;

  // 解析JWT的过期时间(假设Access Token为JWT格式)
  const payload = JSON.parse(atob(accessToken.split('.')[1]));
  const expiresAt = payload.exp * 1000;
  // 提前5分钟触发刷新
  const refreshDelay = expiresAt - 5 * 60 * 1000 - Date.now();

  if (refreshDelay > 0) {
    setTimeout(async () => {
      try {
        await api.post('/refresh-token');
        // 刷新成功后重置定时器
        setupRefreshTimer();
      } catch (err) {
        console.error('预刷新Token失败:', err);
      }
    }, refreshDelay);
  }
};

// 页面加载时启动定时器
useEffect(() => {
  setupRefreshTimer();
}, []);

export default api;

三、解决过期Access Token的安全顾虑

你担心的「过期Access Token泄露后被用来刷新」是不存在的——正确的刷新流程根本不依赖Access Token,只认数据库中存储的Refresh Token。前端在Access Token过期后,是用Refresh Token去请求新的Access Token,而非过期的Access Token。

核心安全措施:

  • Refresh Token必须存储在数据库,同时标记过期时间、吊销状态(用户登出时立即吊销对应记录)。
  • Refresh Token的Cookie必须设置HttpOnly、Secure、SameSite=Strict,防止XSS和CSRF攻击。
  • 启用滚动刷新:每次刷新Access Token时生成新的Refresh Token替换旧的,即使旧Token泄露也会失效。
  • Access Token使用短有效期(15-30分钟),缩小泄露后的风险窗口。

四、额外优化建议

  • 不要将Refresh Token存在前端localStorage,必须用HttpOnly Cookie,避免XSS窃取。
  • 限制用户有效Refresh Token数量:每个用户最多保留1-2个有效Token(比如仅允许当前设备登录),降低多设备登录的安全风险。
  • 给Refresh Token设置合理有效期(比如7天),到期后强制用户重新登录。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 23:20:11