# 知习 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. 权限与越权防护 ### 角色体系 | 角色 | 权限范围 | |------|---------| | `USER` | 访问自己的资源 | | `ADMIN` | 用户管理、数据查看、反馈管理 | | `SUPER_ADMIN` | 全部权限(含 ADMIN) | 角色层级:`SUPER_ADMIN` ⊃ `ADMIN` ⊃ `USER` 实现文件: | 文件 | 作用 | |------|------| | `common/types/role.enum.ts` | Role 枚举 + `ROLE_HIERARCHY` + `hasRole()` | | `common/decorators/roles.decorator.ts` | `@Roles(Role.ADMIN)` 装饰器 | | `common/guards/roles.guard.ts` | 全局 `RolesGuard`,校验用户 role | 用法: ```ts @Get('admin/users') @Roles(Role.ADMIN) // 仅 ADMIN 及以上可访问 async listUsers() {} ``` `RolesGuard` 已注册为全局 `APP_GUARD`(在 JwtAuthGuard 之后执行),默认不需要特殊角色即可访问的路由不加 `@Roles()` 即可。 ### 资源归属校验 所有用户资源操作必须校验 `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] 敏感信息不在日志中打印原则已确立