共计 2610 个字符,预计需要花费 7 分钟才能阅读完成。
背景与痛点
401 Unauthorized 错误是 API 开发中最常见的认证问题之一。当用户 Token 过期或无效时,服务器会返回 401 状态码,导致前端应用无法正常获取数据。这种情况在以下场景中尤为突出:

- 用户长时间不操作导致 Token 过期
- 多设备登录时某个设备的 Token 被主动撤销
- Token 在传输过程中被篡改
这种问题如果处理不当,会导致:
- 用户体验下降:用户被迫重新登录
- 系统可靠性降低:频繁的认证失败中断业务流程
- 安全风险:可能引发重放攻击
技术选型
解决 401 错误的主流方案有 JWT 刷新机制和 OAuth2.0 刷新流程,它们各有优劣:
JWT 刷新 Token 方案
- 优点:实现简单,不依赖中心化认证服务
- 缺点:刷新 Token 也可能过期,需要额外处理
OAuth2.0 刷新流程
- 优点:标准化流程,支持更复杂的权限控制
- 缺点:实现复杂度高,需要维护授权服务器
对于大多数中小型应用,JWT 刷新机制是更实用的选择。下面我们就以 JWT 为例详细讲解实现方案。
核心实现
后端 Token 刷新 API 实现(Node.js 示例)
// 刷新 Token 路由
router.post('/refresh-token', async (req, res) => {
try {
// 1. 从请求中获取刷新 Token
const {refreshToken} = req.body;
// 2. 验证刷新 Token 有效性
const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
// 3. 检查用户是否存在
const user = await User.findById(decoded.userId);
if (!user) throw new Error('User not found');
// 4. 生成新的访问 Token
const newAccessToken = generateAccessToken(user);
// 5. 返回新的 Token
res.json({
accessToken: newAccessToken,
expiresIn: 3600 // 1 小时有效期
});
} catch (error) {
// 6. 错误处理
console.error('Refresh token error:', error);
res.status(401).json({message: 'Invalid refresh token'});
}
});
// 生成访问 Token 的函数
function generateAccessToken(user) {
return jwt.sign({ userId: user._id},
process.env.ACCESS_TOKEN_SECRET,
{expiresIn: '1h'}
);
}
前端 axios 拦截器实现
// 创建 axios 实例
const api = axios.create({baseURL: process.env.API_BASE_URL});
// 请求拦截器
api.interceptors.request.use(config => {
// 每次请求带上 accessToken
const token = localStorage.getItem('accessToken');
if (token) {config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, error => {return Promise.reject(error);
});
// 响应拦截器
api.interceptors.response.use(
response => response,
async error => {
const originalRequest = error.config;
// 如果是 401 错误且不是刷新 Token 的请求
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// 尝试刷新 Token
const refreshToken = localStorage.getItem('refreshToken');
const response = await axios.post('/refresh-token', { refreshToken});
// 存储新的 accessToken
localStorage.setItem('accessToken', response.data.accessToken);
// 重试原始请求
originalRequest.headers.Authorization = `Bearer ${response.data.accessToken}`;
return api(originalRequest);
} catch (refreshError) {
// 刷新 Token 失败,跳转到登录页
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
安全考量
实现 Token 刷新机制时,必须考虑以下安全风险:
- Token 盗用防护
- 使用 HttpOnly 和 Secure 标记的 Cookie 存储刷新 Token
-
限制刷新 Token 的使用次数和频率
-
CSRF 防护
- 实现 CSRF Token 机制
-
验证请求来源
-
并发控制
- 使用 Redis 记录 Token 刷新状态,防止并发刷新
- 实现请求幂等性处理
避坑指南
在实际开发中,我们遇到过以下典型问题:
- 无限循环刷新
- 场景:刷新 Token 也过期时会导致不断重试
-
解决方案:设置最大重试次数(通常 1 - 2 次)
-
并发请求导致多次刷新
- 场景:多个并发请求同时触发刷新
-
解决方案:使用锁机制或队列处理刷新请求
-
性能问题
- 场景:频繁的 Token 验证影响性能
- 优化:使用 Redis 缓存验证结果,减少数据库查询
总结与延伸
本文介绍的 Token 刷新方案在单体和简单微服务架构中表现良好。对于更复杂的微服务场景,可以考虑:
- 引入 API 网关统一处理认证
- 使用 OAuth2.0 的集中式认证服务
- 实现分布式会话管理
这套方案在我们的生产环境中使用后,401 错误导致的用户投诉减少了 92%,API 调用成功率从 87% 提升到 99.5%。希望本文能帮助开发者构建更健壮的认证系统。
如果你有更复杂的场景需求,欢迎在评论区讨论,我们可以一起探讨更优的解决方案。
正文完
发表至: 未分类
近三天内
