170 lines
4.6 KiB
Markdown
170 lines
4.6 KiB
Markdown
# 知习 api-server 安全基线
|
||
|
||
> v0.1 安全设计文档。本后端存储用户资料、知识库、上传文件、主动回忆回答、AI 分析结果和学习记录,第一版必须建立基础安全边界。
|
||
|
||
---
|
||
|
||
## 1. 全局安全中间件
|
||
|
||
| 措施 | 实现 | 文件 |
|
||
|------|------|------|
|
||
| helmet | `app.use(helmet())` 设置安全 HTTP 头 | `src/main.ts` |
|
||
| CORS | 仅允许配置域名。生产环境仅允许 `longde.cloud` | `src/main.ts` |
|
||
| body size limit | JSON 请求体最大 10MB | `src/main.ts` |
|
||
| 异常过滤 | 生产环境不返回 stack trace | `src/common/filters/global-exception.filter.ts` |
|
||
|
||
---
|
||
|
||
## 2. 认证与 Token
|
||
|
||
### JWT
|
||
|
||
- `accessToken`: JWT,1 小时过期
|
||
- `refreshToken`: 128 位随机 hex,入库只存 SHA-256 hash
|
||
- logout 时 `revokedAt = now()` 撤销所有 refresh token
|
||
- `/users/me` 及其所有子路由强制 `@UseGuards(JwtAuthGuard)`
|
||
|
||
```
|
||
POST /auth/apple → 返回 accessToken + refreshToken
|
||
POST /auth/refresh → 消耗旧 refreshToken,发放新 token pair(rotation)
|
||
POST /auth/logout → 撤销该用户所有 refresh token
|
||
```
|
||
|
||
### 存储安全
|
||
|
||
```
|
||
refresh_tokens.tokenHash = SHA-256(实际 token)
|
||
数据库中永远不存明文 refreshToken
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 权限与越权防护
|
||
|
||
### 资源归属校验
|
||
|
||
所有用户资源操作必须校验 `userId` 归属:
|
||
|
||
```ts
|
||
// src/common/utils/security.util.ts
|
||
export async function findByIdAndUserId(delegate, id, userId, resourceName)
|
||
export function ensureOwnership(record, userId, resourceName)
|
||
```
|
||
|
||
### 需校验的资源
|
||
|
||
| 资源 | 校验字段 |
|
||
|------|---------|
|
||
| KnowledgeBase | `userId` |
|
||
| KnowledgeItem | `userId` |
|
||
| LearningSession | `userId` |
|
||
| ActiveRecallAnswer | `userId` |
|
||
| AiAnalysisJob | `userId` |
|
||
| AiAnalysisResult | `userId` |
|
||
| FocusItem | `userId` |
|
||
| ReviewCard | `userId` |
|
||
| ReviewLog | `userId` |
|
||
| DocumentImport | `userId` |
|
||
|
||
---
|
||
|
||
## 4. 参数校验
|
||
|
||
- 全局 `StrictValidationPipe`:
|
||
- `whitelist: true` — 自动剥离未声明字段
|
||
- `forbidNonWhitelisted: true` — 未知字段返回 400
|
||
- 字符串字段最大长度 5000 字符
|
||
- 分页 DTO: page≥1, limit 1-100
|
||
|
||
---
|
||
|
||
## 5. 限流(Redis)
|
||
|
||
| 场景 | Key | 限制 |
|
||
|------|-----|------|
|
||
| 登录 | `rate:ip:{ip}:login:{date}` | 20次/IP/天 |
|
||
| 反馈 | `rate:ip:{ip}:feedback:hourly` | 5次/IP/时 |
|
||
| AI 分析 | `rate:user:{userId}:ai:daily:{date}` | 50次/用户/天 |
|
||
| 文件上传 | `rate:user:{userId}:upload:hourly` | 10次/用户/时 |
|
||
|
||
实现: `src/common/utils/rate-limit.service.ts`
|
||
|
||
---
|
||
|
||
## 6. 文件上传安全
|
||
|
||
| 措施 | 说明 |
|
||
|------|------|
|
||
| 类型白名单 | PDF, Word, Excel, 纯文本, Markdown, CSV, PNG, JPEG, WebP |
|
||
| 大小限制 | 最大 20MB |
|
||
| 随机文件名 | `sanitizeFilename()` 生成随机 key,不信任用户原始文件名 |
|
||
| 默认私有 | 所有文件默认私有访问 |
|
||
| 路径隔离 | `users/{userId}/...` |
|
||
|
||
---
|
||
|
||
## 7. Redis 安全使用
|
||
|
||
- 不存核心业务结果(用户资料/知识点/AI分析结果等必须在 MySQL)
|
||
- 队列任务只存 `jobId`/`userId` 等引用 ID
|
||
- 所有临时 key 必须设置 TTL
|
||
- 防重复提交锁必须有 TTL,解锁校验 token
|
||
- 不在 Redis 中存 token 明文
|
||
|
||
---
|
||
|
||
## 8. COS 安全使用
|
||
|
||
- Bucket 默认私有读写
|
||
- 后端不向前端暴露 SecretId/SecretKey
|
||
- 下载私有文件通过签名 URL
|
||
- 上传路径按 `users/{userId}/{randomKey}` 组织
|
||
- 预留临时上传 URL(STS)机制
|
||
|
||
---
|
||
|
||
## 9. Swagger 安全
|
||
|
||
- 开发环境默认开启
|
||
- 生产环境默认关闭
|
||
- 生产环境如需开启,必须配置 Basic Auth(`SWAGGER_USER`/`SWAGGER_PASSWORD`)
|
||
- 生产环境手动设置 `ENABLE_SWAGGER=true`
|
||
|
||
---
|
||
|
||
## 10. 数据库安全
|
||
|
||
- 不使用 root 连接业务
|
||
- 业务账号 `zhixi_user` 仅需 SELECT/INSERT/UPDATE/DELETE
|
||
- 迁移账号和业务账号分离(`prisma db push` 与运行时连接帐号可不同)
|
||
- 数据库自动备份建议: `mysqldump zhixi | gzip > backup-$(date +%Y%m%d).sql.gz`
|
||
|
||
### 日志中禁止打印
|
||
|
||
```
|
||
DATABASE_URL(含密码)
|
||
JWT_SECRET
|
||
AI_API_KEY
|
||
COS SecretKey
|
||
用户完整 refreshToken
|
||
用户上传文件的完整内容
|
||
Authorization header
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 安全检查清单
|
||
|
||
- [x] helmet 已启用
|
||
- [x] CORS 仅允许白名单域名
|
||
- [x] JWT + refresh token rotation + hash 存储
|
||
- [x] logout 撤销 refresh token
|
||
- [x] 所有用户数据接口需要认证
|
||
- [x] 资源所有权校验工具已就绪
|
||
- [x] StrictValidationPipe 全局启用(whitelist + forbidNonWhitelisted)
|
||
- [x] Redis 限流已实现
|
||
- [x] 文件类型/大小白名单
|
||
- [x] 全局异常过滤器生产环境不暴露 stack trace
|
||
- [x] Swagger 生产环境默认关闭
|
||
- [x] 敏感信息不在日志中打印原则已确立
|