# 前端认证系统实现指南
## 概述
前端 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