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:
407
docs/auth-architecture.md
Normal file
407
docs/auth-architecture.md
Normal 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_token(24h)
|
||||
TokenMgr->>Redis: 存储 refresh_token(7天)
|
||||
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. TokenManager(Token 管理器)
|
||||
|
||||
**职责**:
|
||||
- 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 Token:24 小时自动过期
|
||||
- Refresh Token:7 天自动过期
|
||||
- 修改密码后立即撤销所有旧 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 |
|
||||
|--------|-------------|-----|
|
||||
| 撤销能力 | ✅ 立即生效 | ❌ 无法撤销 |
|
||||
| 性能 | ✅ 5ms(Redis 查询) | ✅ 0ms(本地验证) |
|
||||
| 存储负担 | ⚠️ Redis 内存 | ✅ 无服务端存储 |
|
||||
| 灵活性 | ✅ 可存储复杂信息 | ⚠️ Payload 有大小限制 |
|
||||
| 适用场景 | B 端系统(需要撤销) | C 端系统(高并发) |
|
||||
|
||||
**决策理由**:
|
||||
- B 端用户数量有限(< 1000),Redis 内存负担可接受
|
||||
- 修改密码、账号禁用等场景需要立即撤销 Token
|
||||
- 需要存储完整的用户上下文信息(ShopID、EnterpriseID 等)
|
||||
|
||||
### 为什么使用双令牌机制?
|
||||
|
||||
**问题**:如果只有一个 Token:
|
||||
- 短生命周期:用户频繁掉线,体验差
|
||||
- 长生命周期:Token 泄露风险增加
|
||||
|
||||
**解决方案**:
|
||||
- Access Token(24小时):用于 API 访问,频繁传输,短生命周期降低泄露风险
|
||||
- Refresh Token(7天):用于刷新 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 验证:< 5ms(Redis GET 操作)
|
||||
- Token 生成:< 10ms(Redis SET + SADD 操作)
|
||||
- Token 撤销:< 5ms(Redis 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
|
||||
**维护者**: 君鸿卡管系统开发团队
|
||||
Reference in New Issue
Block a user