You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8.5 KiB
8.5 KiB
Changelog: 认证失效处理优化
日期: 2026-02-09
类型: Bug Fix / Enhancement
影响范围: 前端认证机制
问题背景
现象
用户在项目页面中,如果遇到以下情况:
- 项目被删除(404)→ token 被错误清除 → 跳转登录页(❌ 错误)
- 权限不足(403)→ token 被错误清除 → 跳转登录页(❌ 错误)
- Token 失效(401)→ 重复调用 clearAuth → 多次跳转登录页(❌ 错误)
根本原因
- 误判认证错误:403(权限不足)和 404(资源不存在)不应清除 token
- 重复清除:并发 401 请求多次触发 clearAuth
- 跳转逻辑混乱:
client.ts和AuthContext都有跳转逻辑
解决方案
核心改进
1. 严格区分 HTTP 状态码
| 状态码 | 含义 | 是否清除 Token | 跳转页面 |
|---|---|---|---|
| 401 | 未认证/Token 失效 | ✅ 是 | /login |
| 403 | 权限不足(已登录) | ❌ 否 | 无(显示错误提示) |
| 404 | 资源不存在 | ❌ 否 | /workspace |
理由:
- 401:Token 过期或无效,必须重新登录
- 403:用户已登录但无权访问该资源,应保持登录状态
- 404:资源被删除,跳转到 workspace 而非登录页
2. 使用 sessionStorage 持久化标记
// AuthContext.tsx
const clearAuth = useCallback((reason?: string) => {
const isClearing = sessionStorage.getItem('auth_clearing');
if (isClearing === 'true') {
console.log('⚠️ 认证已失效,跳过重复清除');
return;
}
sessionStorage.setItem('auth_clearing', 'true'); // 会话级保护
// ... 清除 token + 跳转
}, [setUser, queryClient]);
优点:
- 会话期间全局生效,防止任何重复调用
- 刷新页面后自动清除(不影响正常使用)
- 无需定时器,避免竞态条件
3. 请求拦截器增强
// client.ts
apiClient.interceptors.request.use((config) => {
const isClearing = sessionStorage.getItem('auth_clearing');
if (isClearing === 'true') {
console.log('🚫 认证已失效,拒绝请求:', config.url);
return Promise.reject(new Error('认证失效,请重新登录'));
}
// ... 添加 token
});
效果:
- 一旦检测到 401,立即阻止所有后续请求
- 避免并发请求重复触发 clearAuth
- 减少无效请求,降低服务器负载
4. 响应拦截器分层处理
// 401: 清除 token + 跳转登录
if (errorData.code === 401) {
clearAuth(`API 错误 401: ${message}`);
}
// 403: 保持登录状态
if (errorData.code === 403) {
console.warn('⚠️ 权限不足 (403)');
// 不清除 token
}
// 404: 跳转 workspace
if (errorData.code === 404) {
console.warn('⚠️ 资源不存在 (404)');
window.location.href = '/workspace';
}
5. 统一跳转逻辑
- ✅ 保留:
AuthContext.clearAuth中的 401 跳转逻辑 - ✅ 新增:404 跳转到
/workspace - ❌ 移除:多处重复跳转逻辑
6. 登录时清除标记
5. 登录时清除标记
// AuthContext.tsx
const login = useCallback(async (token: string, userData: User) => {
sessionStorage.removeItem('auth_clearing'); // 清除失效标记
localStorage.setItem('token', token);
// ...
}, [setUser]);
作用:
- 登录成功后恢复正常请求
- 双重保险:确保标记不会残留
6. 登录页清除标记
// LoginPage.tsx
useEffect(() => {
// 清除认证失效标记(到达登录页即可重新登录)
sessionStorage.removeItem('auth_clearing');
// ... 检测已登录状态
}, [navigate, from]);
作用:
- 到达登录页时立即清除标记
- 允许用户重新登录(不被请求拦截器拒绝)
- 确保登录流程正常进行
变更文件
1. client/src/contexts/AuthContext.tsx
- ✅ 使用
sessionStorage.getItem('auth_clearing')替代clearingRef - ✅ 移除
useRef导入和 100ms 定时器 - ✅ 在
login中清除auth_clearing标记 - ✅ 在
clearAuth中统一处理跳转逻辑
2. client/src/services/api/client.ts
- ✅ 请求拦截器:检测
auth_clearing标记,拒绝新请求 - ✅ 响应拦截器:分层处理 401/403/404
- 401:调用
clearAuth()(清除 token + 跳转登录) - 403:仅 console.warn(不清除 token)
- 404:跳转
/workspace(不清除 token)
- 401:调用
- ✅ 兼容旧格式(
error.response.status)
3. client/src/lib/error-utils.ts
- ✅ 更新
isAuthError():仅 401 返回 true(不包含 403) - ✅ 增加注释说明:403 是权限不足但仍是登录状态
4. client/src/pages/LoginPage.tsx
- ✅ 在
useEffect中清除auth_clearing标记(到达登录页时) - ✅ 确保登录请求不被拦截器拒绝
5. client/src/lib/auth-manager.ts
- ✅ 移除降级逻辑(
localStorage.removeItem('token')) - ✅ 未注册时不执行任何操作(避免状态不一致)
测试场景
✅ 场景 1:Token 失效(401)
操作:调用一个返回 401 的 API
预期:
- 执行
clearAuth一次 - 清除 token + 用户状态
- 跳转到登录页
- 设置
auth_clearing标记
✅ 场景 2:并发 5 个 401 请求
操作:同时发起 5 个返回 401 的 API 请求
预期:
- 第 1 个请求触发
clearAuth - 其余 4 个请求检测到标记,直接被拦截(不再触发 clearAuth)
- 只跳转一次登录页
- Console 显示 4 次
🚫 认证已失效,拒绝请求
✅ 场景 3:权限不足(403)
操作:调用一个返回 403 的 API
预期:
- 不清除 token(用户仍是登录状态)
- 不跳转登录页
- Console 显示
⚠️ 权限不足 (403) - 用户可继续访问其他有权限的资源
✅ 场景 4:资源不存在(404)
操作:访问一个已被删除的项目
预期:
- 不清除 token
- 跳转到
/workspace(而非登录页) - Console 显示
⚠️ 资源不存在 (404) - 用户可在 workspace 中选择其他项目
✅ 场景 5:401 后尝试发起新请求
操作:
- 触发 401 错误
- 在跳转前,手动调用其他 API 预期:
- 新请求被拦截,返回
认证失效,请重新登录错误 - 不会发送到服务器
✅ 场景 6:登录页加载时清除标记
操作:
- 触发 401(设置
auth_clearing = true) - 跳转到登录页 预期:
- 登录页加载时自动清除
auth_clearing标记 - Console 显示
✅ LoginPage: 清除 auth_clearing 标记 - 登录请求不被拦截器拒绝
✅ 场景 7:登录成功后恢复
操作:
- 触发 401(设置
auth_clearing = true) - 跳转到登录页
- 输入手机号 + 验证码登录 预期:
- 登录成功后清除
auth_clearing标记 - 后续 API 请求正常发送
- 请求拦截器不再拒绝请求
影响评估
✅ 正面影响
- 正确区分错误类型:403/404 不再错误清除 token
- 优化用户体验:404 跳转 workspace 而非登录页
- 彻底解决重复清除:并发 401 只触发一次 clearAuth
- 减少无效请求:阻止认证失效后的所有新请求
⚠️ 潜在风险
- sessionStorage 依赖:部分浏览器隐私模式可能禁用 sessionStorage
- 缓解措施:保留
isClearing状态作为备用标记
- 缓解措施:保留
- 标记未清除:极端情况下(页面崩溃),标记可能残留
- 缓解措施:刷新页面后自动清除(sessionStorage 特性)
后续优化建议
-
增强用户体验
- 403 错误时显示友好提示(如 Toast:"您没有权限访问此资源")
- 404 错误时显示友好提示(如 Toast:"资源不存在,已返回工作区")
- 提供"联系管理员"按钮(403 场景)
-
监控和日志
- 上报 401/403/404 频率(识别异常行为)
- 记录
auth_clearing触发时间和原因
-
测试覆盖
- 添加 E2E 测试:模拟并发 401 请求
- 添加单元测试:验证
clearAuth防重入逻辑 - 添加集成测试:验证 403/404 不清除 token
参考
- 相关文件:
client/src/contexts/AuthContext.tsxclient/src/services/api/client.tsclient/src/lib/auth-manager.tsclient/src/lib/error-utils.ts
- 关联文档:
- RFC 135: 统一响应格式
- ADR: 认证架构设计
作者: Claude
审阅: 待审阅
状态: ✅ 已实施