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

Changelog: 认证失效处理优化

日期: 2026-02-09
类型: Bug Fix / Enhancement
影响范围: 前端认证机制


问题背景

现象

用户在项目页面中,如果遇到以下情况:

  1. 项目被删除(404)→ token 被错误清除 → 跳转登录页( 错误)
  2. 权限不足(403)→ token 被错误清除 → 跳转登录页( 错误)
  3. Token 失效(401)→ 重复调用 clearAuth → 多次跳转登录页( 错误)

根本原因

  • 误判认证错误:403(权限不足)和 404(资源不存在)不应清除 token
  • 重复清除:并发 401 请求多次触发 clearAuth
  • 跳转逻辑混乱client.tsAuthContext 都有跳转逻辑

解决方案

核心改进

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)
  • 兼容旧格式(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 后尝试发起新请求

操作

  1. 触发 401 错误
  2. 在跳转前,手动调用其他 API 预期
  • 新请求被拦截,返回 认证失效,请重新登录 错误
  • 不会发送到服务器

场景 6:登录页加载时清除标记

操作

  1. 触发 401(设置 auth_clearing = true
  2. 跳转到登录页 预期
  • 登录页加载时自动清除 auth_clearing 标记
  • Console 显示 ✅ LoginPage: 清除 auth_clearing 标记
  • 登录请求不被拦截器拒绝

场景 7:登录成功后恢复

操作

  1. 触发 401(设置 auth_clearing = true
  2. 跳转到登录页
  3. 输入手机号 + 验证码登录 预期
  • 登录成功后清除 auth_clearing 标记
  • 后续 API 请求正常发送
  • 请求拦截器不再拒绝请求

影响评估

正面影响

  1. 正确区分错误类型:403/404 不再错误清除 token
  2. 优化用户体验:404 跳转 workspace 而非登录页
  3. 彻底解决重复清除:并发 401 只触发一次 clearAuth
  4. 减少无效请求:阻止认证失效后的所有新请求

⚠️ 潜在风险

  1. sessionStorage 依赖:部分浏览器隐私模式可能禁用 sessionStorage
    • 缓解措施:保留 isClearing 状态作为备用标记
  2. 标记未清除:极端情况下(页面崩溃),标记可能残留
    • 缓解措施:刷新页面后自动清除(sessionStorage 特性)

后续优化建议

  1. 增强用户体验

    • 403 错误时显示友好提示(如 Toast:"您没有权限访问此资源")
    • 404 错误时显示友好提示(如 Toast:"资源不存在,已返回工作区")
    • 提供"联系管理员"按钮(403 场景)
  2. 监控和日志

    • 上报 401/403/404 频率(识别异常行为)
    • 记录 auth_clearing 触发时间和原因
  3. 测试覆盖

    • 添加 E2E 测试:模拟并发 401 请求
    • 添加单元测试:验证 clearAuth 防重入逻辑
    • 添加集成测试:验证 403/404 不清除 token

参考

  • 相关文件
    • client/src/contexts/AuthContext.tsx
    • client/src/services/api/client.ts
    • client/src/lib/auth-manager.ts
    • client/src/lib/error-utils.ts
  • 关联文档
    • RFC 135: 统一响应格式
    • ADR: 认证架构设计

作者: Claude
审阅: 待审阅
状态: 已实施