2026-05-01 20:56:25 +08:00
|
|
|
|
# 身份核验与数据权限策略
|
|
|
|
|
|
|
|
|
|
|
|
> 版本:v1.0 | 状态:已生效
|
|
|
|
|
|
> 关联:tech/INTERFACE.md、PRODUCTION_PHASE1_STATUS.md
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 身份核验
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 核验场景
|
|
|
|
|
|
|
|
|
|
|
|
客服系统需要处理两类身份核验:
|
|
|
|
|
|
|
|
|
|
|
|
| 场景 | 说明 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| 用户身份核验 | 验证用户提供的邮箱/手机与注册信息匹配(用于敏感操作如退款查询) |
|
|
|
|
|
|
| 客服身份核验 | 验证运营后台操作者的身份(防止越权操作) |
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 用户身份核验
|
|
|
|
|
|
|
|
|
|
|
|
**接口**(`tech/INTERFACE.md` 定义):
|
|
|
|
|
|
|
|
|
|
|
|
| 接口 | 路径 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| 身份校验 | `GET /internal/supply/users/verify?email={email}` | 校验用户身份是否匹配 |
|
|
|
|
|
|
| 配额查询 | `GET /internal/runtime/quota?user_id={uid}` | 查询用户配额 |
|
|
|
|
|
|
| Token 消耗查询 | `GET /internal/runtime/token-usage?user_id={uid}&window=1d` | 查询 Token 消耗 |
|
|
|
|
|
|
| 错误日志 | `GET /internal/runtime/error-logs?user_id={uid}&limit=5` | 查询错误日志 |
|
|
|
|
|
|
|
|
|
|
|
|
**当前状态**:上述接口**已定义但外部依赖(supply-api / token-runtime)尚未联调**,实际调用可能失败。
|
|
|
|
|
|
|
|
|
|
|
|
**核验流程**:
|
|
|
|
|
|
1. 用户发起敏感操作(如查询退款状态)
|
|
|
|
|
|
2. 系统要求用户输入邮箱 + 验证码
|
|
|
|
|
|
3. 调用 supply-api 校验邮箱是否匹配用户 ID
|
|
|
|
|
|
4. 匹配成功后执行操作,否则拒绝
|
|
|
|
|
|
|
|
|
|
|
|
### 1.3 身份核验失败处理
|
|
|
|
|
|
|
|
|
|
|
|
| 失败次数 | 处理方式 |
|
|
|
|
|
|
|----------|----------|
|
|
|
|
|
|
| 1-2 次 | 返回 `CS_IDT_4002`(验证码错误),允许重试 |
|
|
|
|
|
|
| 3 次 | 返回 `CS_SES_4003`(身份校验已锁定),锁定 15 分钟 |
|
|
|
|
|
|
| 锁定期间 | 所有身份核验请求返回 403,持续 15min 后自动解锁 |
|
|
|
|
|
|
|
|
|
|
|
|
> **注**:失败计数和锁定机制**当前未落地**(P0 缺口),身份校验只返回匹配结果,不做计数锁定。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 数据权限策略
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 权限基本原则
|
|
|
|
|
|
|
|
|
|
|
|
- 用户**只能查询自己的**会话、工单、Token 消耗数据
|
|
|
|
|
|
- 客服**只能操作被分配的**工单
|
|
|
|
|
|
- 管理员可以查看所有数据,但不得泄露给未授权第三方
|
|
|
|
|
|
- 审计日志**不可篡改**,所有敏感操作均需记录
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 客服操作权限
|
|
|
|
|
|
|
|
|
|
|
|
| 操作 | agent | supervisor | admin |
|
|
|
|
|
|
|------|-------|------------|-------|
|
|
|
|
|
|
| 查看自己被分配的工单 | ✅ | ✅ | ✅ |
|
|
|
|
|
|
| 查看所有工单 | ❌ | ✅ | ✅ |
|
|
|
|
|
|
| assign 工单 | 仅自己的 | ✅ | ✅ |
|
|
|
|
|
|
| resolve 工单 | 仅自己的 | ✅ | ✅ |
|
|
|
|
|
|
| 查看转人工统计 | ❌ | ✅ | ✅ |
|
|
|
|
|
|
| 查看运营大盘 | ❌ | ✅ | ✅ |
|
|
|
|
|
|
| 敏感操作(退款) | ❌ | ✅ | ✅ |
|
|
|
|
|
|
|
2026-05-04 13:44:39 +08:00
|
|
|
|
> **注**:Phase 1 已落地最小 header-based 鉴权与角色校验(`X-CS-Actor-ID` / `X-CS-Actor-Role`),用于保护 ticket/session 后台接口;完整 RBAC、用户级数据隔离与统一身份体系仍未落地,仍需在后续阶段补齐。
|
2026-05-01 20:56:25 +08:00
|
|
|
|
|
|
|
|
|
|
### 2.3 跨用户数据隔离
|
|
|
|
|
|
|
|
|
|
|
|
**当前状态**:`tech/INTERFACE.md` 中各接口的 user_id 隔离**依赖调用方传入正确的 user_id**,后端不做强制校验。
|
|
|
|
|
|
|
|
|
|
|
|
**缺失项(P0)**:
|
|
|
|
|
|
- 所有查询类接口(sessions、tickets、quota 等)应强制要求带上 `user_id`,后端校验 `user_id` 归属,不允许跨用户查询
|
|
|
|
|
|
- 客服操作工单时,后端应校验工单的 `user_id` 与当前操作者的权限范围
|
|
|
|
|
|
|
|
|
|
|
|
**建议方案**(待 TechLead 评审):
|
|
|
|
|
|
```
|
|
|
|
|
|
// 中间件层增强
|
|
|
|
|
|
func AuthMiddleware(next http.Handler) http.Handler {
|
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
claims := getJWTClaims(r)
|
|
|
|
|
|
ctx := context.WithValue(r.Context(), "user_id", claims.UserID)
|
|
|
|
|
|
ctx = context.WithValue(ctx, "role", claims.Role)
|
|
|
|
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理器层校验
|
|
|
|
|
|
func (h *TicketHandler) GetTicket(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
|
userID := r.Context().Value("user_id")
|
|
|
|
|
|
ticketID := mux.Vars(r)["id"]
|
|
|
|
|
|
ticket := h.store.GetTicket(ticketID)
|
|
|
|
|
|
|
|
|
|
|
|
role := r.Context().Value("role")
|
|
|
|
|
|
if role != "admin" && role != "supervisor" && ticket.UserID != userID {
|
|
|
|
|
|
writeError(w, "CS_AUTH_4001", 403) // 越权访问
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Webhook 身份校验
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 已落地
|
|
|
|
|
|
|
|
|
|
|
|
- **HMAC 签名校验**(`webhook_security.go`):验证请求来自合法渠道
|
|
|
|
|
|
- **时间戳防重放**(`webhook_security.go`):防止 replay attack
|
|
|
|
|
|
- **幂等去重**(`dedup_store.go`):防止重复消息
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 待补充
|
|
|
|
|
|
|
|
|
|
|
|
| 项目 | 优先级 | 说明 |
|
|
|
|
|
|
|------|--------|------|
|
|
|
|
|
|
| webhook 速率限制 | P1 | 防止恶意刷请求 |
|
|
|
|
|
|
| 渠道级独立 webhook 路由 | P0 | INTERFACE 定义 `/webhook/{channel}`,当前统一入口 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 敏感数据处理
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 敏感字段
|
|
|
|
|
|
|
|
|
|
|
|
| 字段 | 处理方式 |
|
|
|
|
|
|
|------|----------|
|
|
|
|
|
|
| 用户邮箱 | 脱敏展示(后三位 + `@` 前的后三位),如 `t***@gmail.com` |
|
|
|
|
|
|
| 用户手机 | 脱敏展示(后四位),如 `***-****-1234` |
|
|
|
|
|
|
| API Key | 仅返回前缀后四字符,如 `sk-****-abcd` |
|
|
|
|
|
|
| 退款金额 | 日志脱敏,接口明文返回(须登录态) |
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 当前状态
|
|
|
|
|
|
|
|
|
|
|
|
敏感数据脱敏**当前未落地**,所有字段明文返回。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 审计日志与权限审计
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 已落地
|
|
|
|
|
|
|
|
|
|
|
|
- **审计日志持久化**(`audit_store.go`):写入 PostgreSQL `cs_audit_logs` 表
|
|
|
|
|
|
- **fail-closed**:审计写入失败时整体请求返回错误
|
|
|
|
|
|
- **source_ip / actor_id**:记录操作来源(actor_id 当前有默认值 fallback)
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 待补充
|
|
|
|
|
|
|
|
|
|
|
|
| 项目 | 优先级 | 说明 |
|
|
|
|
|
|
|------|--------|------|
|
|
|
|
|
|
| 安全拒绝事件审计 | P0 | 签名失败、时间戳失败不记审计 |
|
|
|
|
|
|
| 工单状态流转审计 | P0 | assign/resolve 未写审计 |
|
|
|
|
|
|
| source_ip 字段缺失 | P0 | audit_store 当前未写 source_ip |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 当前版本状态
|
|
|
|
|
|
|
|
|
|
|
|
- **本文档版本**:v1.0
|
|
|
|
|
|
- **生效日期**:2026-04-30
|
|
|
|
|
|
- **下次审查**:RBAC 权限模型落地后
|