1 changed files with 215 additions and 0 deletions
@ -0,0 +1,215 @@ |
|||||
|
# 登录注册系统设计 |
||||
|
|
||||
|
**日期:** 2026/05/22 |
||||
|
**状态:** 已批准,待实现 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 1. 核心架构:Strategy 模式 |
||||
|
|
||||
|
``` |
||||
|
AuthService (统一入口) |
||||
|
├── PasswordStrategy ← 现在实现 |
||||
|
└── OAuthStrategy ← 接口预留,以后实现 Google/GitHub 等 |
||||
|
``` |
||||
|
|
||||
|
- `AuthService` 持有所有 Strategy 实例,通过 `type` 参数选择使用哪个 Strategy |
||||
|
- `IAuthStrategy` 接口定义 `authenticate()` 和 `generateTokens()` |
||||
|
- 以后加 OAuth 只新增 `GoogleStrategy extends OAuthStrategy`,`AuthService` 无需改动 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 2. 数据表扩展 |
||||
|
|
||||
|
### users 表扩展字段 |
||||
|
|
||||
|
| 字段 | 类型 | 说明 | |
||||
|
|---|---|---| |
||||
|
| `email` | text | unique,加索引 | |
||||
|
| `emailVerified` | integer (bool) | 邮箱是否验证 | |
||||
|
| `passwordHistory` | text (JSON) | 最近 5 个密码 hash,防复用 | |
||||
|
| `failedLoginAttempts` | integer | 连续失败次数 | |
||||
|
| `lockoutUntil` | integer (timestamp) | 锁定截止时间 | |
||||
|
| `lastLoginAt` | integer (timestamp) | 最后登录时间 | |
||||
|
| `lastLoginIp` | text | 最后登录 IP | |
||||
|
| `refreshToken` | text | 当前 refresh token hash(兼容旧字段) | |
||||
|
|
||||
|
### user_sessions 表(新建) |
||||
|
|
||||
|
| 字段 | 类型 | 说明 | |
||||
|
|---|---|---| |
||||
|
| `id` | text | session id (uuid) | |
||||
|
| `userId` | integer | 外键 references users.id | |
||||
|
| `refreshTokenHash` | text | token hash(bcrypt) | |
||||
|
| `userAgent` | text | 浏览器 UA | |
||||
|
| `ip` | text | 登录 IP | |
||||
|
| `createdAt` | integer (timestamp_ms) | 创建时间 | |
||||
|
| `expiresAt` | integer (timestamp_ms) | 过期时间 | |
||||
|
| `revokedAt` | integer (timestamp_ms) | 撤销时间,nullable | |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 3. 安全机制 |
||||
|
|
||||
|
### 密码 |
||||
|
|
||||
|
- bcrypt 加密,cost factor 12 |
||||
|
- 注册:密码强度校验(8位以上,大小写+数字+特殊字符) |
||||
|
- 登录失败 5 次锁定 15 分钟 |
||||
|
- 密码历史:最近 5 个密码不允许复用 |
||||
|
|
||||
|
### Token |
||||
|
|
||||
|
- Access Token:JWT,15 分钟有效期,payload 含 userId + sessionId + role |
||||
|
- Refresh Token:UUIDv4,7 天有效期,存储 hash 到 user_sessions |
||||
|
- Refresh Token 放 HTTP Only + Secure + SameSite=Strict Cookie |
||||
|
- Refresh Token Rotation:每次 refresh 颁发新 token,旧 token 作废 |
||||
|
|
||||
|
### 登录安全 |
||||
|
|
||||
|
- 记录 lastLoginAt / lastLoginIp |
||||
|
- 失败次数累计 + 锁定机制 |
||||
|
- 通用限流:同一 IP 1 分钟内最多 5 次登录尝试(所有 auth 路由共享) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 4. API 路由 |
||||
|
|
||||
|
``` |
||||
|
POST /api/auth/register 注册 |
||||
|
POST /api/auth/login 登录 |
||||
|
POST /api/auth/logout 登出 |
||||
|
POST /api/auth/refresh 刷新 token |
||||
|
GET /api/auth/me 获取当前用户 |
||||
|
``` |
||||
|
|
||||
|
### 请求响应格式 |
||||
|
|
||||
|
**POST /api/auth/register** |
||||
|
```json |
||||
|
// Request |
||||
|
{ "email": "user@example.com", "password": "Str0ng!", "username": "john" } |
||||
|
|
||||
|
// Response 201 |
||||
|
{ "user": { "id": 1, "email": "user@example.com", "username": "john" } } |
||||
|
``` |
||||
|
|
||||
|
**POST /api/auth/login** |
||||
|
```json |
||||
|
// Request |
||||
|
{ "email": "user@example.com", "password": "Str0ng!" } |
||||
|
|
||||
|
// Response 200 |
||||
|
{ |
||||
|
"user": { "id": 1, "email": "...", "username": "john", "role": "user" }, |
||||
|
"accessToken": "<jwt>" |
||||
|
} |
||||
|
// RefreshToken 放 HTTP Only Cookie |
||||
|
``` |
||||
|
|
||||
|
**POST /api/auth/logout** |
||||
|
```json |
||||
|
// Response 200 |
||||
|
{ "success": true } |
||||
|
``` |
||||
|
|
||||
|
**POST /api/auth/refresh** |
||||
|
```json |
||||
|
// Response 200(Cookie 自动发送) |
||||
|
{ "accessToken": "<new-jwt>" } |
||||
|
``` |
||||
|
|
||||
|
**GET /api/auth/me** |
||||
|
```json |
||||
|
// Response 200 |
||||
|
{ "user": { "id": 1, "email": "...", "username": "john", "role": "user" } } |
||||
|
``` |
||||
|
|
||||
|
### 错误响应格式 |
||||
|
```json |
||||
|
{ "error": { "code": "INVALID_CREDENTIALS", "message": "邮箱或密码错误" } } |
||||
|
``` |
||||
|
|
||||
|
错误码: |
||||
|
- `INVALID_CREDENTIALS` — 登录失败 |
||||
|
- `ACCOUNT_LOCKED` — 账户锁定 |
||||
|
- `WEAK_PASSWORD` — 密码强度不足 |
||||
|
- `EMAIL_EXISTS` — 邮箱已注册 |
||||
|
- `TOKEN_EXPIRED` — access token 过期 |
||||
|
- `SESSION_REVOKED` — session 已撤销 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 5. 目录结构 |
||||
|
|
||||
|
``` |
||||
|
server/ |
||||
|
├── api/auth/ |
||||
|
│ ├── register.post.ts |
||||
|
│ ├── login.post.ts |
||||
|
│ ├── logout.post.ts |
||||
|
│ ├── refresh.post.ts |
||||
|
│ └── me.get.ts |
||||
|
├── service/auth/ |
||||
|
│ ├── index.ts ← AuthService |
||||
|
│ ├── strategies/ |
||||
|
│ │ ├── index.ts ← StrategyRegistry |
||||
|
│ │ ├── password.strategy.ts |
||||
|
│ │ └── oauth.strategy.ts ← 抽象类预留 |
||||
|
│ └── lib/ |
||||
|
│ ├── jwt.ts |
||||
|
│ ├── password.ts |
||||
|
│ └── rate-limit.ts |
||||
|
├── middleware/ |
||||
|
│ └── auth.ts ← 请求认证中间件 |
||||
|
└── types/ |
||||
|
└── auth.ts |
||||
|
``` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 6. OAuth2 预留接口 |
||||
|
|
||||
|
```typescript |
||||
|
interface IAuthStrategy { |
||||
|
readonly type: 'password' | 'oauth'; |
||||
|
authenticate(credentials: unknown): Promise<User>; |
||||
|
generateTokens(user: User, sessionId: string): TokenPair; |
||||
|
revokeSession(sessionId: string): Promise<void>; |
||||
|
} |
||||
|
|
||||
|
abstract class OAuthStrategy implements IAuthStrategy { |
||||
|
abstract readonly provider: 'google' | 'github' | string; |
||||
|
abstract readonly type: 'oauth'; |
||||
|
|
||||
|
abstract authenticate(oauthToken: string): Promise<User>; |
||||
|
generateTokens(user: User, sessionId: string): TokenPair { /* ... */ } |
||||
|
revokeSession(sessionId: string): Promise<void> { /* ... */ } |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
以后实现 OAuth 时: |
||||
|
1. 新增 `GoogleStrategy extends OAuthStrategy` |
||||
|
2. 在 `StrategyRegistry` 注册 `google: new GoogleStrategy()` |
||||
|
3. `AuthService` 无需改动 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 7. 实现顺序 |
||||
|
|
||||
|
1. Schema 迁移(users 字段扩展 + user_sessions 表) |
||||
|
2. `PasswordStrategy` 实现(密码加密、校验、历史) |
||||
|
3. `AuthService` + Strategy 模式 |
||||
|
4. API 路由(注册、登录、登出、刷新、获取用户) |
||||
|
5. 中间件 + JWT 验证 |
||||
|
6. 限流 + 锁定机制 |
||||
|
7. 测试覆盖 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## 8. 依赖组件(已有) |
||||
|
|
||||
|
- bcryptjs — 密码加密 |
||||
|
- Zod — 输入校验 |
||||
|
- drizzle-orm — 数据库 ORM |
||||
|
- @libsql/client — SQLite 客户端 |
||||
Loading…
Reference in new issue