Next.js Token错误

527次阅读
没有评论

共计 3090 个字符,预计需要花费 8 分钟才能阅读完成。

​前言:当 Token 开始玩“躲猫猫”​

在前后端分离架构中,Token 管理就像一场精密的双人舞。最近我在开发一个 Next.js 应用时,遭遇了​​Token 刷新后无法同步到 localStorage 和 Cookie​​的诡异问题。这个 Bug 让我在前后端之间反复横跳,最终发现这是一场由​​环境边界认知偏差​​引发的技术博弈。


​一、案发现场:Token 的离奇失踪​

需求很简单:​​当 Token 过期时,自动刷新并更新存储,保障用户无感知登录​​。初始代码看似完美:

ts 复制// 拦截器核心逻辑
const newToken = await refreshToken(oldToken);
localStorage.setItem('accessToken', newToken); // 控制台打印成功但未生效
setCookie({res}, 'accessToken', newToken); // 服务端操作 Cookie

​诡异现象:​

  1. 浏览器控制台显示 newToken111 但无newToken222
  2. 新 Token 无法存入 localStorage
  3. 服务端 Cookie 更新但前端请求未携带新 Token

​二、破案关键:那些被忽视的“边界”​

​1. 前端拦截器的时空陷阱​

ts 复制// 错误示例:服务端环境操作 localStorage
localStorage.setItem('token', newToken); // 服务端执行时报错

// 正确姿势:环境隔离
if (typeof window !== 'undefined') {localStorage.setItem('token', newToken);
}

​教训:​​ Next.js 的 API 路由运行在 Node.js 环境,直接操作浏览器 API 会导致运行时错误。

​2. Cookie 的“量子纠缠”特性​

ts 复制// 错误配置:服务端设置 httpOnly Cookie
setCookie({res}, 'accessToken', token, {httpOnly: true // 前端无法读取});

// 修复方案:开放读取权限
setCookie({res}, 'accessToken', token, {
  httpOnly: false,
  secure: process.env.NODE_ENV === 'production'
});

​教训:​​ 需要前端读取的 Cookie 必须关闭httpOnly,但要做好 XSS 防护。

​3. 请求头的“记忆偏差”​

ts 复制// 错误逻辑:更新存储但未同步请求头
localStorage.setItem('token', newToken);
// 后续请求仍携带旧 Token

// 修复方案:双重同步
apiClient.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;

​教训:​​ Token 存储与请求头需保持“量子纠缠”般的即时同步。


​三、终极解决方案

​1. 前端拦截器改造​

ts 复制apiClient.interceptors.response.use(null, async (error) => {if (error.response.status === 403) {const newToken = await refreshToken();
    
    // 环境安全写入
    if (typeof window !== 'undefined') {localStorage.setItem('token', newToken);
    }
    
    // 请求头实时更新
    apiClient.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
    
    // 重试队列处理
    failedQueue.forEach(cb => cb(newToken));
  }
  return Promise.reject(error);
});

​2. 服务端 Cookie 同步策略​

ts 复制// 在 API 路由处理层
export const ApiAxios = async ({nextRes, ...props}) => {const response = await apiClient(props);
  
  if (response.data?.accessToken) {// 服务端写 Cookie
    setCookie({res: nextRes}, 'accessToken', token, { 
      httpOnly: false,
      path: '/'
    });
    
    // 通知前端存储
    response.data._syncStorage = true;
  }
  return response;
};

​3. 前端监听同步机制​

tsx 复制// 在应用入口组件
useEffect(() => {const syncToken = ({ data}) => {if (data?._syncStorage) {localStorage.setItem('token', data.accessToken);
    }
  };
  window.addEventListener('message', syncToken);
  return () => window.removeEventListener('message', syncToken);
}, []);

​四、六大避坑指南​

  1. ​环境隔离原则​​ts 复制// 安全写法 const isClient = () => typeof window !== 'undefined';
  2. ​Cookie 属性四象限​​场景推荐配置前端可读 httpOnly: false 生产环境 secure: true 跨域共享 sameSite: 'Lax' 长期有效maxAge: 2592000 (30 天)
  3. ​Token 同步双通道​​图片代码graph TD A[Token 刷新] --> B[前端 localStorage] A --> C[服务端 Set-Cookie] B --> D[请求头 Authorization] C --> E[自动携带 Cookie]Token 刷新前端 localStorage 服务端 Set-Cookie 请求头 Authorization 自动携带 Cookie
  4. ​错误处理三阶响应​​ts 复制try {// 业务请求 } catch (error) {if (isTokenExpired(error)) {// 一级处理:自动刷新 } else if (isNetworkError(error)) {// 二级处理:重试机制 } else {// 三级处理:跳转兜底页 } }
  5. ​监控体系四要素​​ts 复制const monitorToken = () => {track('TOKEN_UPDATE', { source: location.href, timestamp: Date.now(), storage: localStorage.getItem('token') ? 1 : 0, cookie: document.cookie.includes('token') ? 1 : 0 }); };

​五、存储的本质​

这次踩坑经历让我深刻认识到:​​在前端世界中,存储不是静态的仓库,而是流动的河​​。Token 作为身份凭证,需要在以下维度保持平衡:

维度 技术要求 用户体验
时效性 自动刷新机制 无感知登录
安全性 HTTPS + HttpOnly 无频繁验证弹窗
一致性 多端同步策略 设备间无缝切换
可观测性 监控埋点 + 日志追踪 异常及时反馈

​推荐工具链:​

  • JWT Inspector:Token 解析利器
  • Cookies Manager:Cookie 调试神器
  • Next.js Middleware:请求处理中间层

​后记:​​ 每一次痛苦的调试,都是对技术本质的更深理解(喜)。

正文完
 0
评论(没有评论)