# 前端认证系统实现指南 ## 概述 前端 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. 启动后端服务器 ```bash cd backend python main.py ``` ### 2. 启动前端开发服务器 ```bash cd web yarn start lq_label ``` ### 3. 访问应用 打开浏览器访问 http://localhost:4200 ### 4. 测试流程 1. **首次访问** - 自动重定向到登录页 2. **注册新用户** - 点击"立即注册"链接 3. **填写注册表单** - 输入用户名、邮箱、密码 4. **自动登录** - 注册成功后自动登录并跳转到首页 5. **使用应用** - 访问项目、任务、标注等功能 6. **Token 自动刷新** - Access token 过期时自动刷新 7. **登出** - 点击右上角用户菜单中的"退出登录" ## 技术实现细节 ### Token 存储 Tokens 和用户信息存储在 localStorage 中: ```typescript // 存储结构 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 过期时: ```typescript // 第一个请求触发 token 刷新 isRefreshing = true; // 其他请求进入队列等待 failedQueue.push({ resolve, reject }); // Token 刷新完成后处理队列 processQueue(); ``` ### 路由保护 ```typescript // 受保护的路由 {/* 应用路由 */} // ProtectedRoute 组件检查认证状态 if (!isAuthenticated) { return ; } ``` ## 样式设计 ### 登录/注册页面 - 渐变背景(紫色主题) - 居中卡片布局 - 圆角和阴影效果 - 响应式设计 - 输入框焦点效果 - 按钮悬停动画 ### 用户菜单 - 头像(用户名首字母) - 用户信息显示 - 下拉动画 - 点击外部关闭 - 主题适配(支持深色模式) ## 安全特性 ### 1. Token 安全 - Access token 存储在 localStorage - 自动附加到请求 header - 15 分钟过期时间 - 自动刷新机制 ### 2. 密码验证 - 最少 8 个字符 - 客户端验证 - 服务器端二次验证 ### 3. 表单验证 - 用户名长度(3-50 字符) - 邮箱格式验证 - 密码确认匹配 - 实时错误提示 ### 4. XSS 防护 - React 自动转义 - 不使用 dangerouslySetInnerHTML - 输入清理 ## 错误处理 ### 网络错误 ```typescript // API 拦截器自动处理 - 显示 toast 错误消息 - 记录到控制台 - 返回格式化错误对象 ``` ### 认证错误 ```typescript // 401 Unauthorized - Token 过期 → 自动刷新 - Token 无效 → 重定向到登录页 - 刷新失败 → 清除数据并重定向 // 403 Forbidden - 显示权限不足消息 ``` ### 表单错误 ```typescript // 客户端验证 - 空字段检查 - 格式验证 - 长度验证 - 密码匹配检查 // 服务器端错误 - 用户名已存在 - 邮箱已存在 - 密码错误 ``` ## 性能优化 ### 1. 状态持久化 - 使用 `atomWithStorage` 自动同步 localStorage - 页面刷新后保持登录状态 ### 2. 请求优化 - Token 刷新时使用请求队列 - 防止并发刷新请求 - 自动重试失败请求 ### 3. 组件优化 - 使用 React.memo(如需要) - 避免不必要的重渲染 - 懒加载路由组件(可选) ## 开发调试 ### 查看存储的 Tokens ```javascript // 在浏览器控制台 console.log(JSON.parse(localStorage.getItem('auth_tokens'))); console.log(JSON.parse(localStorage.getItem('current_user'))); ``` ### 清除认证数据 ```javascript // 在浏览器控制台 localStorage.removeItem('auth_tokens'); localStorage.removeItem('current_user'); location.reload(); ``` ### 测试 Token 刷新 ```javascript // 修改 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.tsx` 的 `ProtectedRoute` 内部添加新的 `` 即可。 ### 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