FRONTEND_AUTH_GUIDE.md 9.7 KB

前端认证系统实现指南

概述

前端 JWT 认证系统已成功实现,提供完整的用户注册、登录、自动 token 刷新和路由保护功能。

已实现的功能

✅ 状态管理(Jotai Atoms)

文件: src/atoms/auth-atoms.ts

  • authTokensAtom - JWT tokens 存储(localStorage 持久化)
  • currentUserAtom - 当前用户信息(localStorage 持久化)
  • isAuthenticatedAtom - 认证状态(派生 atom)
  • isAdminAtom - 管理员状态(派生 atom)
  • loginAtom - 登录操作(写入 atom)
  • logoutAtom - 登出操作(写入 atom)
  • updateTokensAtom - 更新 tokens(写入 atom)

✅ 认证服务

文件: src/services/auth-service.ts

  • register() - 用户注册
  • login() - 用户登录
  • refreshToken() - 刷新 access token
  • getCurrentUser() - 获取当前用户信息
  • logout() - 客户端登出

✅ Axios 拦截器

文件: src/services/api.ts

请求拦截器:

  • 自动从 localStorage 读取 access token
  • 自动附加到 Authorization header
  • 跳过认证端点(/api/auth/*)

响应拦截器:

  • 检测 401 错误和 token 过期
  • 自动使用 refresh token 刷新 access token
  • 实现请求队列机制(防止并发刷新)
  • Token 刷新失败时清除认证数据并重定向到登录页

✅ UI 组件

登录表单 (src/components/login-form/)

  • 用户名和密码输入
  • 表单验证
  • 加载状态
  • 错误处理
  • 登录成功后自动跳转

注册表单 (src/components/register-form/)

  • 用户名、邮箱、密码输入
  • 密码确认
  • 完整的表单验证(长度、格式)
  • 加载状态
  • 错误处理
  • 注册成功后自动登录并跳转

路由保护 (src/components/protected-route/)

  • 检查认证状态
  • 未认证时重定向到登录页
  • 保存原始访问路径

用户菜单 (src/components/user-menu/)

  • 显示当前用户信息
  • 显示用户角色(管理员/标注员)
  • 下拉菜单
  • 登出按钮

✅ 路由配置

文件: src/app/app.tsx

公开路由:

  • /login - 登录页面
  • /register - 注册页面
  • 已登录用户访问时自动重定向到首页

受保护路由:

  • / - 首页
  • /projects - 项目管理
  • /tasks - 任务管理
  • /annotations - 标注管理
  • 所有其他路由
  • 未登录用户访问时自动重定向到登录页

使用方法

1. 启动后端服务器

cd backend
python main.py

2. 启动前端开发服务器

cd web
yarn start lq_label

3. 访问应用

打开浏览器访问 http://localhost:4200

4. 测试流程

  1. 首次访问 - 自动重定向到登录页
  2. 注册新用户 - 点击"立即注册"链接
  3. 填写注册表单 - 输入用户名、邮箱、密码
  4. 自动登录 - 注册成功后自动登录并跳转到首页
  5. 使用应用 - 访问项目、任务、标注等功能
  6. Token 自动刷新 - Access token 过期时自动刷新
  7. 登出 - 点击右上角用户菜单中的"退出登录"

技术实现细节

Token 存储

Tokens 和用户信息存储在 localStorage 中:

// 存储结构
localStorage.setItem('auth_tokens', JSON.stringify({
  access_token: "...",
  refresh_token: "...",
  token_type: "bearer"
}));

localStorage.setItem('current_user', JSON.stringify({
  id: "user_xxx",
  username: "testuser",
  email: "test@example.com",
  role: "annotator",
  created_at: "2024-01-22T..."
}));

自动 Token 刷新流程

1. API 请求返回 401 错误
   ↓
2. 检查错误类型是否为 token_expired
   ↓
3. 检查是否已在刷新中(防止并发)
   ↓
4. 使用 refresh_token 调用 /api/auth/refresh
   ↓
5. 更新 localStorage 中的 tokens
   ↓
6. 重试原始请求(使用新 token)
   ↓
7. 处理队列中的其他请求

请求队列机制

当多个请求同时遇到 token 过期时:

// 第一个请求触发 token 刷新
isRefreshing = true;

// 其他请求进入队列等待
failedQueue.push({ resolve, reject });

// Token 刷新完成后处理队列
processQueue();

路由保护

// 受保护的路由
<ProtectedRoute>
  <Layout>
    <Routes>
      {/* 应用路由 */}
    </Routes>
  </Layout>
</ProtectedRoute>

// ProtectedRoute 组件检查认证状态
if (!isAuthenticated) {
  return <Navigate to="/login" state={{ from: location }} replace />;
}

样式设计

登录/注册页面

  • 渐变背景(紫色主题)
  • 居中卡片布局
  • 圆角和阴影效果
  • 响应式设计
  • 输入框焦点效果
  • 按钮悬停动画

用户菜单

  • 头像(用户名首字母)
  • 用户信息显示
  • 下拉动画
  • 点击外部关闭
  • 主题适配(支持深色模式)

安全特性

1. Token 安全

  • Access token 存储在 localStorage
  • 自动附加到请求 header
  • 15 分钟过期时间
  • 自动刷新机制

2. 密码验证

  • 最少 8 个字符
  • 客户端验证
  • 服务器端二次验证

3. 表单验证

  • 用户名长度(3-50 字符)
  • 邮箱格式验证
  • 密码确认匹配
  • 实时错误提示

4. XSS 防护

  • React 自动转义
  • 不使用 dangerouslySetInnerHTML
  • 输入清理

错误处理

网络错误

// API 拦截器自动处理
- 显示 toast 错误消息
- 记录到控制台
- 返回格式化错误对象

认证错误

// 401 Unauthorized
- Token 过期 → 自动刷新
- Token 无效 → 重定向到登录页
- 刷新失败 → 清除数据并重定向

// 403 Forbidden
- 显示权限不足消息

表单错误

// 客户端验证
- 空字段检查
- 格式验证
- 长度验证
- 密码匹配检查

// 服务器端错误
- 用户名已存在
- 邮箱已存在
- 密码错误

性能优化

1. 状态持久化

  • 使用 atomWithStorage 自动同步 localStorage
  • 页面刷新后保持登录状态

2. 请求优化

  • Token 刷新时使用请求队列
  • 防止并发刷新请求
  • 自动重试失败请求

3. 组件优化

  • 使用 React.memo(如需要)
  • 避免不必要的重渲染
  • 懒加载路由组件(可选)

开发调试

查看存储的 Tokens

// 在浏览器控制台
console.log(JSON.parse(localStorage.getItem('auth_tokens')));
console.log(JSON.parse(localStorage.getItem('current_user')));

清除认证数据

// 在浏览器控制台
localStorage.removeItem('auth_tokens');
localStorage.removeItem('current_user');
location.reload();

测试 Token 刷新

// 修改 access_token 为无效值
const tokens = JSON.parse(localStorage.getItem('auth_tokens'));
tokens.access_token = 'invalid_token';
localStorage.setItem('auth_tokens', JSON.stringify(tokens));

// 然后发起任何 API 请求,应该自动刷新

常见问题

Q: 为什么刷新页面后还是登录状态?

A: 使用 atomWithStorage 将认证数据持久化到 localStorage,页面刷新后自动恢复。

Q: Token 过期后会发生什么?

A: 自动使用 refresh token 刷新 access token,用户无感知。如果 refresh token 也过期,会重定向到登录页。

Q: 如何测试未登录状态?

A: 点击用户菜单中的"退出登录",或在控制台清除 localStorage。

Q: 如何添加新的受保护路由?

A: 在 app.tsxProtectedRoute 内部添加新的 <Route> 即可。

Q: 如何自定义登录/注册页面样式?

A: 修改对应的 .module.scss 文件,使用 CSS 变量支持主题切换。

下一步增强

可选功能

  • 记住我功能(延长 token 有效期)
  • 社交登录(OAuth)
  • 邮箱验证
  • 密码重置
  • 用户资料编辑
  • 头像上传
  • 双因素认证(2FA)
  • 登录历史记录
  • 设备管理

性能优化

  • 路由懒加载
  • 组件代码分割
  • Token 预刷新(在过期前刷新)
  • 离线支持(Service Worker)

文件清单

新增文件

web/apps/lq_label/src/
├── atoms/
│   └── auth-atoms.ts                    # 认证状态管理
├── services/
│   └── auth-service.ts                  # 认证 API 服务
├── components/
│   ├── login-form/
│   │   ├── login-form.tsx              # 登录表单组件
│   │   ├── login-form.module.scss      # 登录表单样式
│   │   └── index.ts
│   ├── register-form/
│   │   ├── register-form.tsx           # 注册表单组件
│   │   ├── register-form.module.scss   # 注册表单样式
│   │   └── index.ts
│   ├── protected-route/
│   │   ├── protected-route.tsx         # 路由保护组件
│   │   └── index.ts
│   └── user-menu/
│       ├── user-menu.tsx               # 用户菜单组件
│       ├── user-menu.module.scss       # 用户菜单样式
│       └── index.ts

修改文件

web/apps/lq_label/src/
├── atoms/
│   └── index.ts                         # 导出 auth-atoms
├── services/
│   ├── index.ts                         # 导出 auth-service
│   └── api.ts                           # 添加 token 拦截器
├── app/
│   └── app.tsx                          # 添加认证路由
└── components/
    └── layout/
        └── top-bar.tsx                  # 添加用户菜单

总结

前端 JWT 认证系统已完全实现,包括:

  • ✅ 完整的用户注册和登录流程
  • ✅ 自动 token 管理和刷新
  • ✅ 路由保护和重定向
  • ✅ 用户界面和交互
  • ✅ 错误处理和用户反馈
  • ✅ 状态持久化
  • ✅ 主题适配

系统已经可以投入使用!🎉


实现日期: 2024-01-22
状态: ✅ 完成
文档版本: 1.0