feat: 完成B端认证系统和商户管理模块测试补全

主要变更:
- 新增B端认证系统(后台+H5):登录、登出、Token刷新、密码修改
- 完善商户管理和商户账号管理功能
- 补全单元测试(ShopService: 72.5%, ShopAccountService: 79.8%)
- 新增集成测试(商户管理+商户账号管理)
- 归档OpenSpec提案(add-shop-account-management, implement-b-end-auth-system)
- 完善文档(使用指南、API文档、认证架构说明)

测试统计:
- 13个测试套件,37个测试用例,100%通过率
- 平均覆盖率76.2%,达标

OpenSpec验证:通过(strict模式)
This commit is contained in:
2026-01-15 18:15:17 +08:00
parent 7ccd3d146c
commit 18f35f3ef4
64 changed files with 11875 additions and 242 deletions

407
docs/auth-architecture.md Normal file
View File

@@ -0,0 +1,407 @@
# B 端认证系统架构说明
本文档描述君鸿卡管系统 B 端认证的架构设计、技术决策和安全机制。
---
## 系统概述
### 核心特性
- **双令牌机制**Access Token短期+ Refresh Token长期
- **Redis 存储**Token 存储在 Redis支持快速撤销
- **多平台支持**后台管理Admin和 H5 移动端
- **用户类型隔离**:不同平台限制不同的用户类型访问
- **无状态验证**Token 验证无需查询数据库
### 技术栈
| 组件 | 技术选型 | 理由 |
|------|----------|------|
| Token 生成 | UUID v4 | 高度随机,不可预测 |
| Token 存储 | Redis | 快速查询,支持 TTL 自动过期 |
| 密码哈希 | bcrypt | 慢哈希算法,抗暴力破解 |
| HTTP 框架 | Fiber v2 | 高性能,类 Express API |
| 数据库 | PostgreSQL | ACID 保证,可靠性高 |
---
## 架构图
### 认证流程
```mermaid
sequenceDiagram
participant Client as 客户端
participant Handler as AuthHandler
participant Service as AuthService
participant TokenMgr as TokenManager
participant Redis as Redis
participant DB as PostgreSQL
Note over Client,DB: 1. 登录流程
Client->>Handler: POST /api/admin/login
Handler->>Service: Login(username, password)
Service->>DB: 查询账号信息
DB-->>Service: 返回账号(含密码哈希)
Service->>Service: bcrypt 验证密码
Service->>DB: 查询用户权限
DB-->>Service: 返回权限列表
Service->>TokenMgr: GenerateTokenPair(userInfo)
TokenMgr->>Redis: 存储 access_token24h
TokenMgr->>Redis: 存储 refresh_token7天
TokenMgr-->>Service: 返回 token 对
Service-->>Handler: 返回 token + 用户信息
Handler-->>Client: 200 OK + JSON响应
Note over Client,DB: 2. 访问受保护接口
Client->>Handler: GET /api/admin/me + Bearer Token
Handler->>TokenMgr: ValidateAccessToken(token)
TokenMgr->>Redis: GET auth:token:{token}
Redis-->>TokenMgr: 返回 TokenInfo
TokenMgr-->>Handler: 返回用户上下文
Handler->>Service: GetCurrentUser(userID)
Service->>DB: 查询用户信息
DB-->>Service: 返回用户数据
Service-->>Handler: 返回用户+权限
Handler-->>Client: 200 OK + JSON响应
Note over Client,DB: 3. Token 刷新
Client->>Handler: POST /api/admin/refresh-token
Handler->>Service: RefreshToken(refresh_token)
Service->>TokenMgr: ValidateRefreshToken(token)
TokenMgr->>Redis: GET auth:refresh:{token}
Redis-->>TokenMgr: 返回 TokenInfo
TokenMgr->>TokenMgr: GenerateNewAccessToken
TokenMgr->>Redis: 存储新 access_token
TokenMgr-->>Service: 返回新 access_token
Service-->>Handler: 返回新 token
Handler-->>Client: 200 OK + new token
```
### 中间件执行顺序
```
HTTP 请求
[Recover 中间件]
[RequestID 中间件]
[Logger 中间件]
[Auth 中间件] ← 本系统
├─ 提取 Token
├─ 验证 Token调用 TokenManager
├─ 检查用户类型
└─ 设置用户上下文
[路由处理器]
├─ 从 context 获取用户信息
└─ 执行业务逻辑
HTTP 响应
```
---
## 核心组件设计
### 1. TokenManagerToken 管理器)
**职责**
- Token 生成:使用 UUID v4 生成不可预测的 Token
- Token 验证:从 Redis 查询并解析 TokenInfo
- Token 撤销:单个撤销或批量撤销用户所有 Token
- Token 刷新:验证 Refresh Token 并生成新的 Access Token
**数据结构**
```go
type TokenInfo struct {
UserID uint // 用户 ID
UserType int // 用户类型1-4
ShopID uint // 店铺 ID代理商
EnterpriseID uint // 企业 ID企业客户
Username string // 用户名
LoginTime time.Time // 登录时间
Device string // 设备类型
IP string // 登录 IP
}
```
**Redis 存储结构**
```
# Access Token
Key: auth:token:{token_uuid}
Value: JSON(TokenInfo)
TTL: 24 小时
# Refresh Token
Key: auth:refresh:{token_uuid}
Value: JSON(TokenInfo)
TTL: 7 天
# 用户 Token 列表(用于批量撤销)
Key: auth:user:{user_id}:tokens
Value: SET[token1, token2, ...]
TTL: 7 天
```
### 2. AuthService认证服务
**职责**
- 登录验证:查询账号、验证密码、生成 Token
- 权限查询:查询用户的角色和权限列表
- Token 管理:登出、刷新、批量撤销
- 密码管理:修改密码(含旧 Token 撤销)
**依赖注入**
```go
type Service struct {
accountStore *postgres.AccountStore // 账号查询
accountRoleStore *postgres.AccountRoleStore // 账号-角色关联
rolePermStore *postgres.RolePermissionStore // 角色-权限关联
permissionStore *postgres.PermissionStore // 权限查询
tokenManager *auth.TokenManager // Token 管理
logger *zap.Logger // 日志记录
}
```
### 3. Auth Middleware认证中间件
**职责**
- Token 提取:从 `Authorization: Bearer {token}` 提取 Token
- Token 验证:调用 TokenManager 验证合法性
- 用户类型检查:根据平台限制用户类型
- 上下文设置:将用户信息设置到 Fiber 和 Go Context
**配置示例**
```go
// 后台认证中间件
AdminAuth := middleware.Auth(middleware.AuthConfig{
TokenValidator: func(token string) (*middleware.UserContextInfo, error) {
// 验证 token
tokenInfo, err := tokenManager.ValidateAccessToken(ctx, token)
if err != nil {
return nil, errors.New(errors.CodeInvalidToken, "令牌无效")
}
// 检查用户类型:后台只允许 SuperAdmin、Platform、Agent
if tokenInfo.UserType != constants.UserTypeSuperAdmin &&
tokenInfo.UserType != constants.UserTypePlatform &&
tokenInfo.UserType != constants.UserTypeAgent {
return nil, errors.New(errors.CodeForbidden, "权限不足")
}
return &middleware.UserContextInfo{...}, nil
},
SkipPaths: []string{"/api/admin/login", "/api/admin/refresh-token"},
})
```
---
## 安全机制
### 1. 密码安全
**Bcrypt 哈希**
- 使用 bcrypt 算法cost=10存储密码
- 每个密码有唯一的 salt防止彩虹表攻击
- 慢哈希算法,增加暴力破解成本
```go
// 密码哈希(注册时)
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 10)
// 密码验证(登录时)
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
```
### 2. Token 安全
**不可预测性**
- 使用 UUID v4 生成128 位随机数
- 碰撞概率极低(约 1/2^122
**短生命周期**
- Access Token24 小时自动过期
- Refresh Token7 天自动过期
- 修改密码后立即撤销所有旧 Token
**传输安全**
- 仅通过 Authorization 请求头传递(不在 URL 中)
- 生产环境强制 HTTPS
### 3. 用户类型隔离
| 平台 | 允许访问 | 拒绝访问 |
|------|----------|----------|
| 后台 | SuperAdmin(1), Platform(2), Agent(3) | Enterprise(4), PersonalCustomer |
| H5 | Agent(3), Enterprise(4) | SuperAdmin(1), Platform(2), PersonalCustomer |
### 4. 防御措施
**防止暴力破解**
- 计划引入登录失败次数限制(待实现)
- 使用慢哈希算法bcrypt增加单次尝试成本
**防止 Token 泄露**
- Token 不出现在日志中(敏感信息脱敏)
- Token 不出现在 URL 中
- Redis 连接使用密码保护
**防止会话劫持**
- Token 绑定设备和 IP存储在 TokenInfo 中,可用于审计)
- 可选:实现设备指纹验证(待实现)
---
## 设计决策
### 为什么选择 Redis 而非 JWT
| 对比项 | Redis Token | JWT |
|--------|-------------|-----|
| 撤销能力 | ✅ 立即生效 | ❌ 无法撤销 |
| 性能 | ✅ 5msRedis 查询) | ✅ 0ms本地验证 |
| 存储负担 | ⚠️ Redis 内存 | ✅ 无服务端存储 |
| 灵活性 | ✅ 可存储复杂信息 | ⚠️ Payload 有大小限制 |
| 适用场景 | B 端系统(需要撤销) | C 端系统(高并发) |
**决策理由**
- B 端用户数量有限(< 1000Redis 内存负担可接受
- 修改密码、账号禁用等场景需要立即撤销 Token
- 需要存储完整的用户上下文信息ShopID、EnterpriseID 等)
### 为什么使用双令牌机制?
**问题**:如果只有一个 Token
- 短生命周期:用户频繁掉线,体验差
- 长生命周期Token 泄露风险增加
**解决方案**
- Access Token24小时用于 API 访问,频繁传输,短生命周期降低泄露风险
- Refresh Token7天用于刷新 Access Token低频传输长生命周期减少掉线
### 为什么密码修改要撤销所有 Token
**安全原因**
- 假设:用户发现密码泄露,立即修改密码
- 如果不撤销旧 Token攻击者仍可使用旧 Token 访问
**实现**
```go
func (s *Service) ChangePassword(ctx context.Context, userID uint, oldPassword, newPassword string) error {
// 1. 验证旧密码
// 2. 哈希新密码
// 3. 更新数据库
// 4. 撤销所有旧 Token
return s.tokenManager.RevokeAllUserTokens(ctx, userID)
}
```
---
## 性能考量
### Redis 性能
**预期负载**
- 用户数:< 1000
- 每用户平均 Token 数2-3 个
- 总 Token 数:< 3000
- Redis 内存占用:< 3MB每个 TokenInfo 约 1KB
**性能指标**
- Token 验证:< 5msRedis GET 操作)
- Token 生成:< 10msRedis SET + SADD 操作)
- Token 撤销:< 5msRedis DEL 操作)
### 数据库查询优化
**登录流程优化**
1. 账号查询:使用 `username``phone` 索引(< 10ms
2. 权限查询:使用 `account_id` 索引(< 20ms
3. 总耗时:< 50ms
**缓存策略**(待实现):
- 用户权限列表可缓存 30 分钟
- 减少数据库查询压力
---
## 扩展性
### 水平扩展
**无状态设计**
- 认证服务无状态,可水平扩展
- Token 存储在 Redis所有实例共享
**Redis 集群**
- 当前使用单机 Redis
- 需要时可升级为 Redis Cluster 或 Sentinel
### 功能扩展
**可选功能**
- [ ] 设备指纹验证
- [ ] 登录失败次数限制
- [ ] 异地登录提醒
- [ ] 在线设备管理
- [ ] Token 黑名单
---
## 监控和审计
### 关键指标
| 指标 | 说明 | 告警阈值 |
|------|------|----------|
| 登录成功率 | 成功次数 / 总次数 | < 95% |
| Token 验证失败率 | 失败次数 / 总次数 | > 5% |
| Redis 可用性 | Ping 响应时间 | > 10ms |
| Token 平均验证时间 | P95 响应时间 | > 20ms |
### 审计日志
**记录事件**
- 用户登录(成功/失败)
- Token 撤销(单个/批量)
- 密码修改
- 账号状态变更
**日志格式**
```json
{
"level": "info",
"timestamp": "2026-01-15T16:15:00+08:00",
"event": "user_login",
"user_id": 1,
"username": "admin",
"ip": "127.0.0.1",
"device": "web",
"success": true
}
```
---
## 相关文档
- [API 文档](api/auth.md) - 完整的 API 接口说明
- [使用指南](auth-usage-guide.md) - 如何在代码中集成认证
- [错误处理指南](003-error-handling/使用指南.md) - 统一错误处理
---
**文档版本**: v1.0
**最后更新**: 2026-01-15
**维护者**: 君鸿卡管系统开发团队