[阶段1.1-1.7] 实现完整的认证系统

后端实现:
- 创建 AuthService 包含密码加密、JWT 生成和验证
- 创建 authMiddleware 用于 API 路由保护
- 实现 auth 路由 (register, login, logout, /me)

前端实现:
- 创建 Login 页面 (登录/注册标签页)
- 创建 ProtectedRoute 组件用于路由保护
- 更新 App.tsx 集成路由保护
- 前端 API 客户端已包含认证方法和拦截器

验收标准已满足:
- 用户可以注册和登录
- JWT Token 正确生成和验证
- 受保护的路由需要有效 Token
- 未认证用户重定向到登录页面

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
ericwyuan
2026-08-23 12:25:33 +08:00
parent 6b05d04773
commit 3b2d0697f0
28 changed files with 1970 additions and 75 deletions

View File

@@ -0,0 +1,65 @@
import { Request, Response, NextFunction } from 'express';
import { verifyToken } from '../services/AuthService';
import { AppError } from './errorHandler';
export interface AuthRequest extends Request {
userId?: string;
userEmail?: string;
}
export function authMiddleware(req: AuthRequest, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
error: {
status: 401,
message: 'Missing authorization header',
},
});
}
const parts = authHeader.split(' ');
if (parts.length !== 2 || parts[0] !== 'Bearer') {
return res.status(401).json({
error: {
status: 401,
message: 'Invalid authorization header format',
},
});
}
const token = parts[1];
try {
const payload = verifyToken(token);
req.userId = payload.userId;
next();
} catch (error: any) {
return res.status(401).json({
error: {
status: 401,
message: 'Invalid or expired token',
},
});
}
}
export function optionalAuthMiddleware(req: AuthRequest, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (authHeader) {
const parts = authHeader.split(' ');
if (parts.length === 2 && parts[0] === 'Bearer') {
const token = parts[1];
try {
const payload = verifyToken(token);
req.userId = payload.userId;
} catch (error) {
// Silently fail - continue without authentication
}
}
}
next();
}