Files
junhong_cmp_fiber/openspec/changes/archive/2026-01-30-handler-validation-security/design.md
huang 409a68d60b
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m45s
feat: OpenAPI 契约对齐与框架优化
主要变更:
1. OpenAPI 文档契约对齐
   - 统一错误响应字段名为 msg(非 message)
   - 规范 envelope 响应结构(code, msg, data, timestamp)
   - 个人客户路由纳入文档体系(使用 Register 机制)
   - 新增 BuildDocHandlers() 统一管理 handler 构造
   - 确保文档生成的幂等性

2. Service 层错误处理统一
   - 全面替换 fmt.Errorf 为 errors.New/Wrap
   - 统一错误码使用规范
   - Handler 层参数校验不泄露底层细节
   - 新增错误码验证集成测试

3. 代码质量提升
   - 删除未使用的 Task handler 和路由
   - 新增代码规范检查脚本(check-service-errors.sh)
   - 新增注释路径一致性检查(check-comment-paths.sh)
   - 更新 API 文档生成指南

4. OpenSpec 归档
   - 归档 openapi-contract-alignment 变更(63 tasks)
   - 归档 service-error-unify-core 变更
   - 归档 service-error-unify-support 变更
   - 归档 code-cleanup-docs-update 变更
   - 归档 handler-validation-security 变更
   - 同步 delta specs 到主规范文件

影响范围:
- pkg/openapi: 新增 handlers.go,优化 generator.go
- internal/service/*: 48 个 service 文件错误处理统一
- internal/handler/admin: 优化参数校验错误提示
- internal/routes: 个人客户路由改造,删除 task 路由
- scripts: 新增 3 个代码检查脚本
- docs: 更新 OpenAPI 文档(15750+ 行)
- openspec/specs: 同步 3 个主规范文件

破坏性变更:无
向后兼容:是
2026-01-30 11:40:36 +08:00

381 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Handler 层参数校验安全加固 - 设计文档
**功能 ID**: `handler-validation-security-001`
## 设计目标
防止参数校验错误泄露内部实现细节validator 规则、字段名、类型信息),提升 API 安全性。
## 问题分析
### 当前问题
在 Handler 层中,参数解析和验证失败时,直接将底层错误信息(`err.Error()`)拼接后返回给客户端,导致以下安全风险:
1. **泄露 DTO 字段名**`Field validation for 'Username' failed on the 'required' tag`
2. **泄露验证规则**:客户端可以知道哪些字段必填、长度限制、格式要求等
3. **泄露类型信息**`Unmarshal type error: expected=uint got=string field=shop_id`
4. **便于反向工程**:攻击者可以根据错误信息探测 API 内部结构
### 影响范围(基于扫描结果)
```
总计: 32 个 handler 文件11 处错误泄露点
Admin Handler (29 个文件)
├── auth.go (3 处)
│ ├── Login() - 行 35
│ ├── RefreshToken() - 行 80
│ └── ChangePassword() - 行 133
├── role.go (4 处)
│ ├── Create() - 行 39
│ ├── Update() - 行 80
│ ├── AssignPermissions() - 行 136
│ └── RemovePermissions() - 行 197
└── storage.go (1 处)
└── GenerateUploadURL() - 行 32
H5 Handler (3 个文件)
└── auth.go (3 处)
├── Login() - 行 35
├── RefreshToken() - 行 80
└── ChangePassword() - 行 133
```
## 设计方案
### 核心原则
| 原则 | 说明 |
|------|------|
| **对外通用** | 客户端收到的错误消息不包含内部细节 |
| **日志详细** | 服务端日志记录完整的错误信息用于排查 |
| **一致性** | 所有 Handler 使用相同的错误处理模式 |
| **安全性** | 防止通过错误消息进行探测攻击 |
### 修复策略
#### 策略 1批量修复优先
针对已发现的 11 处错误泄露点,优先修复:
```
Phase 1: 修复已知错误点(预估 1h
├── admin/auth.go (3 处)
├── admin/role.go (4 处)
├── admin/storage.go (1 处)
└── h5/auth.go (3 处)
Phase 2: 全量检查(预估 1h
└── 检查其余 28 个文件是否有类似问题
```
#### 策略 2使用模板替换
定义 3 种标准修复模板,确保一致性:
| 场景 | 修复模板 |
|------|---------|
| 参数解析错误 | 模板 A |
| 参数验证错误 | 模板 B |
| 参数格式错误 | 模板 C |
## 技术设计
### 错误处理流程
#### 当前流程(有安全风险)
```mermaid
graph LR
A[Handler 接收请求] --> B[BodyParser/Validate]
B -->|失败| C[拼接 err.Error()]
C --> D[返回详细错误给客户端]
D --> E[❌ 泄露内部细节]
```
#### 修复后流程(安全)
```mermaid
graph LR
A[Handler 接收请求] --> B[BodyParser/Validate]
B -->|失败| C{记录日志}
C --> D[logger.Warn 记录详细错误]
C --> E[返回通用错误消息]
D --> F[✅ 日志包含完整信息]
E --> G[✅ 客户端不泄露细节]
```
### 修复模板
#### 模板 A参数解析错误
```go
// ❌ 修复前
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "参数解析失败: "+err.Error())
}
// ✅ 修复后
if err := c.BodyParser(&req); err != nil {
logger.GetAppLogger().Warn("参数解析失败",
zap.String("path", c.Path()),
zap.String("method", c.Method()),
zap.Error(err),
)
return response.Error(c, 400, errors.CodeInvalidParam, "参数解析失败")
}
```
**关键变更**
- ✅ 添加结构化日志path、method、error
- ✅ 移除 `err.Error()` 拼接
- ✅ 对外返回通用消息
#### 模板 B参数验证错误
```go
// ❌ 修复前
if err := h.validator.Struct(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "参数验证失败: "+err.Error())
}
// ✅ 修复后
if err := h.validator.Struct(&req); err != nil {
logger.GetAppLogger().Warn("参数验证失败",
zap.String("path", c.Path()),
zap.String("method", c.Method()),
zap.Error(err),
)
return errors.New(errors.CodeInvalidParam) // 使用默认 msg"参数验证失败"
}
```
**关键变更**
- ✅ 使用 `errors.New(CodeInvalidParam)` 不传自定义消息
- ✅ 自动使用 errorMessages 映射表中的默认消息
- ✅ validator 详细错误仅记录到日志
#### 模板 C参数格式错误
```go
// ❌ 修复前
page, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
return response.Error(c, 400, errors.CodeInvalidParam, "页码格式错误: "+err.Error())
}
// ✅ 修复后
page, err := strconv.Atoi(c.Query("page", "1"))
if err != nil {
logger.GetAppLogger().Warn("页码参数格式错误",
zap.String("path", c.Path()),
zap.String("page", c.Query("page")),
zap.Error(err),
)
return response.Error(c, 400, errors.CodeInvalidParam, "页码格式错误")
}
```
**关键变更**
- ✅ 日志记录原始参数值(用于排查)
- ✅ 移除错误细节(如 `strconv.Atoi: parsing "abc": invalid syntax`
### 日志记录设计
#### 日志级别
| 场景 | 级别 | 原因 |
|------|------|------|
| 参数解析错误 | `WARN` | 客户端错误,需要记录但不是系统故障 |
| 参数验证错误 | `WARN` | 客户端错误,需要记录但不是系统故障 |
| 参数格式错误 | `WARN` | 客户端错误,需要记录但不是系统故障 |
#### 日志字段
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `level` | string | 日志级别 | `"warn"` |
| `ts` | string | 时间戳 | `"2026-01-30T10:00:00Z"` |
| `msg` | string | 日志消息 | `"参数验证失败"` |
| `path` | string | 请求路径 | `"/api/admin/accounts"` |
| `method` | string | HTTP 方法 | `"POST"` |
| `error` | string | 详细错误 | `"Field validation for 'Username' failed on the 'required' tag"` |
#### 示例日志输出
```json
{
"level": "warn",
"ts": "2026-01-30T10:15:23.456Z",
"msg": "参数验证失败",
"path": "/api/admin/accounts",
"method": "POST",
"error": "Key: 'CreateAccountRequest.Username' Error:Field validation for 'Username' failed on the 'required' tag"
}
```
### 错误响应设计
#### 修复前(泄露细节)
```json
{
"code": 10001,
"msg": "参数验证失败: Field validation for 'Username' failed on the 'required' tag",
"data": null,
"timestamp": "2026-01-30T10:15:23Z"
}
```
**问题**
- ❌ 泄露字段名 `Username`
- ❌ 泄露验证规则 `required`
- ❌ 泄露 DTO 结构 `CreateAccountRequest`
#### 修复后(安全)
```json
{
"code": 10001,
"msg": "参数验证失败",
"data": null,
"timestamp": "2026-01-30T10:15:23Z"
}
```
**改进**
- ✅ 通用错误消息
- ✅ 不泄露内部结构
- ✅ 详细信息在服务端日志
## 执行计划
### Phase 1: 修复已知错误点(优先级:🔴 高)
**工作量**: 1 小时
| 文件 | 错误数 | 修复内容 |
|------|-------|---------|
| `admin/auth.go` | 3 | 使用模板 B 修复 3 处参数验证错误 |
| `admin/role.go` | 4 | 使用模板 B 修复 4 处参数验证错误 |
| `admin/storage.go` | 1 | 检查并修复错误处理(可能需要自定义) |
| `h5/auth.go` | 3 | 使用模板 B 修复 3 处参数验证错误 |
**验证步骤**
1. 每修复一个文件,运行 `go build -o /tmp/test_api ./cmd/api`
2. 使用 `grep` 确认该文件不再包含 `err.Error()` 拼接
### Phase 2: 全量检查(优先级:🟡 中)
**工作量**: 1 小时
检查其余 28 个 handler 文件:
- 搜索所有 `BodyParser``QueryParser``Validate` 调用
- 确认错误处理符合模板 A、B、C
- 发现问题立即修复
**自动化脚本**
```bash
# 检查所有可能的参数校验点
grep -n "BodyParser\|QueryParser\|validator.Struct" internal/handler/admin/*.go internal/handler/h5/*.go
```
### Phase 3: 测试验证(优先级:🔴 高)
**工作量**: 1 小时
1. **集成测试**:补充参数校验失败的测试用例
2. **手动测试**:发送错误参数验证响应格式
3. **日志验证**:确认日志包含完整错误信息
### Phase 4: 文档更新(优先级:🟡 中)
**工作量**: 0.5 小时
1. 更新 `openspec/specs/error-handling/spec.md`
2. 更新 `docs/003-error-handling/使用指南.md`
## 影响评估
### 对外 API 影响
| 影响点 | 变更内容 | Breaking Change |
|--------|---------|-----------------|
| 错误消息 | 从详细错误变为通用消息 | ✅ 是 |
| 错误码 | 不变(仍为 10001 | ❌ 否 |
| HTTP 状态码 | 不变(仍为 400 | ❌ 否 |
| 响应格式 | 不变(仍为 {code, msg, data, timestamp} | ❌ 否 |
### 客户端适配建议
```javascript
// 前端错误处理建议
if (response.code === 10001) {
// ❌ 旧方式:依赖 msg 中的字段名提示
// message.error(response.msg); // "参数验证失败: Field validation for 'Username' failed"
// ✅ 新方式:使用通用提示或前端验证
message.error('请检查输入参数是否完整和正确');
// 或者依赖前端表单验证提前拦截
}
```
### 安全性提升
| 风险 | 修复前 | 修复后 |
|------|-------|-------|
| 字段名泄露 | ✅ 存在 | ❌ 已消除 |
| 验证规则泄露 | ✅ 存在 | ❌ 已消除 |
| 类型信息泄露 | ✅ 存在 | ❌ 已消除 |
| DTO 结构泄露 | ✅ 存在 | ❌ 已消除 |
| 探测攻击风险 | 🔴 高 | 🟢 低 |
### 性能影响
| 指标 | 影响 | 说明 |
|------|------|------|
| 响应时间 | ≈ 0 | 仅增加日志写入(异步) |
| 内存占用 | +0.1% | 日志缓冲区占用可忽略 |
| CPU 占用 | +0.1% | 日志序列化开销可忽略 |
| 磁盘占用 | +10MB/天 | WARN 级别日志增量(自动轮转) |
**结论**:性能影响可忽略,安全性显著提升。
## 后续优化
### 可选优化方向
1. **国际化错误消息**
- 当前返回中文错误消息
- 可根据 `Accept-Language` 返回多语言错误
- 需要扩展 `errorMessages` 映射表
2. **错误码细化**
- 当前所有参数错误都是 `10001`
- 可细化为:`10001` 参数缺失、`10002` 参数格式错误、`10003` 参数值非法
- 便于前端差异化处理
3. **错误追踪**
- 在响应中添加 `request_id` 字段
- 客户端可通过 request_id 联系客服定位问题
- 需修改 `response.Error()` 函数
## 验证清单
- [ ] 所有 11 处错误泄露点已修复
- [ ] 所有 Handler 文件检查完毕
- [ ] `grep -r "err\.Error()" internal/handler/` 无残留(除日志外)
- [ ] 编译通过 `go build -o /tmp/test_api ./cmd/api`
- [ ] 集成测试通过
- [ ] 手动测试验证不泄露字段名
- [ ] 日志包含完整错误信息
- [ ] 文档已更新
- [ ] Code Review 通过
## 参考资料
- [OWASP - Information Leakage](https://owasp.org/www-community/vulnerabilities/Information_Leakage)
- [项目错误处理规范](../../../openspec/specs/error-handling/spec.md)
- [AGENTS.md 错误报错规范](../../../AGENTS.md#错误报错规范必须遵守)