前端 JWT 认证系统已成功实现,提供完整的用户注册、登录、自动 token 刷新和路由保护功能。
文件: 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 tokengetCurrentUser() - 获取当前用户信息logout() - 客户端登出文件: src/services/api.ts
请求拦截器:
响应拦截器:
登录表单 (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 - 标注管理cd backend
python main.py
cd web
yarn start lq_label
打开浏览器访问 http://localhost:4200
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..."
}));
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 />;
}
// API 拦截器自动处理
- 显示 toast 错误消息
- 记录到控制台
- 返回格式化错误对象
// 401 Unauthorized
- Token 过期 → 自动刷新
- Token 无效 → 重定向到登录页
- 刷新失败 → 清除数据并重定向
// 403 Forbidden
- 显示权限不足消息
// 客户端验证
- 空字段检查
- 格式验证
- 长度验证
- 密码匹配检查
// 服务器端错误
- 用户名已存在
- 邮箱已存在
- 密码错误
atomWithStorage 自动同步 localStorage// 在浏览器控制台
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();
// 修改 access_token 为无效值
const tokens = JSON.parse(localStorage.getItem('auth_tokens'));
tokens.access_token = 'invalid_token';
localStorage.setItem('auth_tokens', JSON.stringify(tokens));
// 然后发起任何 API 请求,应该自动刷新
A: 使用 atomWithStorage 将认证数据持久化到 localStorage,页面刷新后自动恢复。
A: 自动使用 refresh token 刷新 access token,用户无感知。如果 refresh token 也过期,会重定向到登录页。
A: 点击用户菜单中的"退出登录",或在控制台清除 localStorage。
A: 在 app.tsx 的 ProtectedRoute 内部添加新的 <Route> 即可。
A: 修改对应的 .module.scss 文件,使用 CSS 变量支持主题切换。
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 认证系统已完全实现,包括:
系统已经可以投入使用!🎉
实现日期: 2024-01-22
状态: ✅ 完成
文档版本: 1.0