api-server/SECURITY.md

170 lines
4.6 KiB
Markdown
Raw Normal View History

# 知习 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`: JWT1 小时过期
- `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 pairrotation
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}` 组织
- 预留临时上传 URLSTS机制
---
## 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] 敏感信息不在日志中打印原则已确立