共计 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
诡异现象:
- 浏览器控制台显示
newToken111但无newToken222 - 新 Token 无法存入 localStorage
- 服务端 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);
}, []);
四、六大避坑指南
- 环境隔离原则ts 复制
// 安全写法 const isClient = () => typeof window !== 'undefined'; - Cookie 属性四象限场景推荐配置前端可读
httpOnly: false生产环境secure: true跨域共享sameSite: 'Lax'长期有效maxAge: 2592000(30 天) - Token 同步双通道图片代码
graph TD A[Token 刷新] --> B[前端 localStorage] A --> C[服务端 Set-Cookie] B --> D[请求头 Authorization] C --> E[自动携带 Cookie]Token 刷新前端 localStorage 服务端 Set-Cookie 请求头 Authorization 自动携带 Cookie - 错误处理三阶响应ts 复制
try {// 业务请求 } catch (error) {if (isTokenExpired(error)) {// 一级处理:自动刷新 } else if (isNetworkError(error)) {// 二级处理:重试机制 } else {// 三级处理:跳转兜底页 } } - 监控体系四要素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:请求处理中间层
后记: 每一次痛苦的调试,都是对技术本质的更深理解(喜)。
正文完
发表至: 技术
2025-01-28