# 设计文档:B 端认证系统 **Change ID**: `implement-b-end-auth-system` --- ## 1. 架构概览 ### 1.1 系统分层 ``` ┌─────────────────────────────────────────────────────────────┐ │ HTTP 层(Fiber) │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ Admin Auth │ │ H5 Auth │ │ Personal │ │ │ │ Handler │ │ Handler │ │ Customer │ │ │ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │ └─────────┼────────────────┼────────────────┼─────────────────┘ │ │ │ ┌─────────▼────────────────▼────────────────▼─────────────────┐ │ 中间件层(Middleware) │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ Admin Auth │ │ H5 Auth │ │ Personal │ │ │ │ Middleware │ │ Middleware │ │ Auth │ │ │ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │ └─────────┼────────────────┼────────────────┼─────────────────┘ │ │ │ │ │ │ ┌─────────▼────────────────▼────────────────▼─────────────────┐ │ 业务逻辑层(Service) │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ Auth │ │ Permission │ │ Personal │ │ │ │ Service │ │ Service │ │ Customer │ │ │ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │ └─────────┼────────────────┼────────────────┼─────────────────┘ │ │ │ ┌─────────▼────────────────▼────────────────▼─────────────────┐ │ 数据访问层(Store) │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ Account │ │ Role │ │ Personal │ │ │ │ Store │ │ Store │ │ Customer │ │ │ └──────┬─────┘ └──────┬─────┘ └──────┬─────┘ │ └─────────┼────────────────┼────────────────┼─────────────────┘ │ │ │ ┌─────────▼────────────────▼────────────────▼─────────────────┐ │ 数据存储层 │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ │ PostgreSQL │ │ Redis │ │ │ │ (账号、角色、权限) │ │ (Token、缓存) │ │ │ └──────────────────────┘ └──────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 1.2 认证方式对比 | 端口 | 用户类型 | 认证方式 | Token 类型 | 存储方式 | |------|---------|---------|-----------|----------| | **Web 后台** | 超级管理员
平台用户
代理账号 | Bearer Token | Redis Token | Redis | | **H5 端** | 代理账号
企业账号 | Bearer Token | Redis Token | Redis | | **个人客户端** | 个人客户 | Bearer Token | JWT | 无状态(自签名) | **设计理由**: - **B 端(Web + H5)**:使用 Redis Token,支持立即登出和撤销 - **C 端(个人客户)**:使用 JWT,减轻服务器压力,适合高并发场景 --- ## 2. 核心模块设计 ### 2.1 Token 管理器(TokenManager) #### 职责 - 生成 access token 和 refresh token - 验证 token 有效性 - 刷新 access token - 撤销 token - 管理用户的所有 token #### 接口设计 ```go package auth type TokenManager struct { rdb *redis.Client accessTokenTTL time.Duration // 24 小时(可配置) refreshTokenTTL time.Duration // 7 天(可配置) } type TokenInfo struct { UserID uint `json:"user_id"` UserType int `json:"user_type"` ShopID uint `json:"shop_id,omitempty"` EnterpriseID uint `json:"enterprise_id,omitempty"` Username string `json:"username"` LoginTime time.Time `json:"login_time"` Device string `json:"device"` // web / h5 / mobile IP string `json:"ip"` } // 生成 token 对 func (m *TokenManager) GenerateTokenPair(ctx context.Context, info *TokenInfo) (accessToken, refreshToken string, err error) // 验证 access token func (m *TokenManager) ValidateAccessToken(ctx context.Context, token string) (*TokenInfo, error) // 验证 refresh token func (m *TokenManager) ValidateRefreshToken(ctx context.Context, token string) (*TokenInfo, error) // 刷新 access token func (m *TokenManager) RefreshAccessToken(ctx context.Context, refreshToken string) (newAccessToken string, err error) // 撤销单个 token func (m *TokenManager) RevokeToken(ctx context.Context, token string) error // 撤销用户的所有 token func (m *TokenManager) RevokeAllUserTokens(ctx context.Context, userID uint) error ``` #### Redis 存储结构 ``` # Access Token Key: auth:token:{uuid} Value: JSON(TokenInfo) TTL: 24h # Refresh Token Key: auth:refresh:{uuid} Value: JSON(TokenInfo) TTL: 7d # 用户 Token 列表(Set) Key: auth:user:{userID}:tokens Value: Set[access_token_uuid, refresh_token_uuid] TTL: 7d ``` **示例**: ``` # Access Token redis> GET auth:token:550e8400-e29b-41d4-a716-446655440000 { "user_id": 123, "user_type": 2, "shop_id": 10, "enterprise_id": 0, "username": "admin", "login_time": "2026-01-15T12:00:00Z", "device": "web", "ip": "192.168.1.1" } # 用户 Token 列表 redis> SMEMBERS auth:user:123:tokens 1) "550e8400-e29b-41d4-a716-446655440000" # access token 2) "660e8400-e29b-41d4-a716-446655440001" # refresh token ``` #### Token 生成流程 ``` GenerateTokenPair() ├─ 1. 生成 access token UUID (uuid.New()) ├─ 2. 生成 refresh token UUID (uuid.New()) ├─ 3. 序列化 TokenInfo 为 JSON ├─ 4. 存储 access token 到 Redis (TTL: 24h) ├─ 5. 存储 refresh token 到 Redis (TTL: 7d) ├─ 6. 将两个 token 添加到用户 token 列表(Set) └─ 7. 返回 access token 和 refresh token ``` #### Token 验证流程 ``` ValidateAccessToken(token) ├─ 1. 从 Redis 查询 auth:token:{token} ├─ 2. 如果不存在或过期 → 返回 CodeInvalidToken ├─ 3. 反序列化 JSON 为 TokenInfo ├─ 4. 验证账号状态(可选,需查询数据库) └─ 5. 返回 TokenInfo ``` #### Token 刷新流程 ``` RefreshAccessToken(refreshToken) ├─ 1. 验证 refresh token (ValidateRefreshToken) ├─ 2. 撤销旧的 access token (RevokeToken) ├─ 3. 生成新的 access token UUID ├─ 4. 存储新 token 到 Redis (TTL: 24h) ├─ 5. 更新用户 token 列表(删除旧 access token,添加新) └─ 6. 返回新 access token ``` #### Token 撤销流程 ``` RevokeToken(token) ├─ 1. 删除 Redis key: auth:token:{token} ├─ 2. 从用户 token 列表中删除该 token └─ 3. 返回成功 RevokeAllUserTokens(userID) ├─ 1. 获取用户 token 列表 (SMEMBERS auth:user:{userID}:tokens) ├─ 2. 批量删除所有 token (DEL auth:token:{uuid} ...) ├─ 3. 删除用户 token 列表 (DEL auth:user:{userID}:tokens) └─ 4. 返回成功 ``` --- ### 2.2 认证服务(AuthService) #### 职责 - 处理登录业务逻辑(验证密码、生成 token) - 处理登出业务逻辑(撤销 token) - 处理 token 刷新业务逻辑 - 处理密码修改业务逻辑 - 查询用户权限列表 #### 接口设计 ```go package auth type Service struct { accountStore AccountStore roleStore RoleStore permissionStore PermissionStore tokenManager *auth.TokenManager logger *zap.Logger } // 登录 func (s *Service) Login(ctx context.Context, req *LoginRequest, clientIP string) (*LoginResponse, error) // 登出 func (s *Service) Logout(ctx context.Context, token string) error // 刷新 token func (s *Service) RefreshToken(ctx context.Context, refreshToken string) (newAccessToken string, error) // 获取当前用户信息和权限 func (s *Service) GetCurrentUser(ctx context.Context, userID uint) (*model.Account, []string, error) // 修改密码 func (s *Service) ChangePassword(ctx context.Context, userID uint, oldPassword, newPassword string) error ``` #### 登录流程设计 ``` Login(username, password, device, clientIP) ├─ 1. 根据用户名查询账号 (accountStore.GetByUsername) │ ├─ 如果不存在 → 返回 CodeInvalidCredentials ("用户名或密码错误") │ └─ 获取账号信息(包含密码哈希、状态) │ ├─ 2. 验证密码 (bcrypt.CompareHashAndPassword) │ ├─ 如果错误 → 返回 CodeInvalidCredentials │ └─ 密码正确 │ ├─ 3. 检查账号状态 │ ├─ status = 0 → 返回 CodeAccountDisabled ("账号已禁用") │ └─ status = 1 → 继续 │ ├─ 4. 构造 TokenInfo │ ├─ UserID = account.ID │ ├─ UserType = account.UserType │ ├─ ShopID = account.ShopID │ ├─ EnterpriseID = account.EnterpriseID │ ├─ Username = account.Username │ ├─ LoginTime = time.Now() │ ├─ Device = device │ └─ IP = clientIP │ ├─ 5. 生成 token 对 (tokenManager.GenerateTokenPair) │ ├─ 生成 access token (UUID) │ ├─ 生成 refresh token (UUID) │ └─ 存储到 Redis │ ├─ 6. 查询用户权限列表 (permissionService.GetUserPermissions) │ ├─ 查询用户的所有角色 (accountRoleStore.GetByAccountID) │ ├─ 查询角色的所有权限 (rolePermissionStore.GetByRoleIDs) │ └─ 返回权限编码列表 (["user:create", "user:update", ...]) │ ├─ 7. 构造响应 │ ├─ AccessToken = access token │ ├─ RefreshToken = refresh token │ ├─ User = account(隐藏密码字段) │ └─ Permissions = 权限列表 │ └─ 8. 返回 LoginResponse ``` **安全考虑**: - ✅ 密码错误和用户名不存在返回相同错误消息,防止用户枚举攻击 - ✅ 密码使用 bcrypt 哈希,成本因子 = 10 - ✅ Token 使用 UUID v4(不可预测) - ✅ 登录时记录 IP 和设备信息 #### 登出流程设计 ``` Logout(token) ├─ 1. 验证 token (tokenManager.ValidateAccessToken) │ ├─ 如果无效 → 返回 CodeInvalidToken │ └─ 获取 TokenInfo │ ├─ 2. 撤销 access token (tokenManager.RevokeToken) │ └─ 删除 Redis key: auth:token:{token} │ ├─ 3. 撤销 refresh token(可选) │ ├─ 从用户 token 列表获取对应的 refresh token │ └─ 删除 Redis key: auth:refresh:{refreshToken} │ └─ 4. 返回成功 ``` **设计选择**: - ❓ **是否同时撤销 refresh token**? - **方案 A**:只撤销 access token,保留 refresh token(允许继续刷新) - **方案 B**:同时撤销 access token 和 refresh token(完全登出) - **推荐**:方案 B(安全性优先,符合用户预期) #### 密码修改流程设计 ``` ChangePassword(userID, oldPassword, newPassword) ├─ 1. 查询账号 (accountStore.GetByID) │ ├─ 如果不存在 → 返回 CodeNotFound │ └─ 获取账号信息(包含密码哈希) │ ├─ 2. 验证旧密码 (bcrypt.CompareHashAndPassword) │ ├─ 如果错误 → 返回 CodeInvalidOldPassword │ └─ 密码正确 │ ├─ 3. 验证新密码格式 │ ├─ 长度 8-32 位 │ ├─ 包含字母和数字 │ └─ 如果不符合 → 返回 CodeInvalidPassword │ ├─ 4. 哈希新密码 (bcrypt.GenerateFromPassword) │ └─ cost = 10 │ ├─ 5. 更新数据库 (accountStore.UpdatePassword) │ └─ 更新 password 字段 │ ├─ 6. 撤销所有旧 token (tokenManager.RevokeAllUserTokens) │ ├─ 删除用户的所有 access token │ ├─ 删除用户的所有 refresh token │ └─ 强制用户重新登录 │ └─ 7. 返回成功 ``` **安全考虑**: - ✅ 修改密码后立即撤销所有旧 token,防止密码泄露后被利用 - ✅ 需要验证旧密码,防止未授权修改 - ✅ 新密码复杂度要求(后续可加强:特殊字符、大小写等) --- ### 2.3 认证中间件(Auth Middleware) #### 职责 - 从请求中提取 token - 验证 token 有效性 - 检查用户类型权限 - 将用户信息注入 context #### 设计架构 **复用现有中间件**:`pkg/middleware/auth.go` 的 `Auth()` 函数 ```go // 通用认证中间件(已存在) func Auth(config AuthConfig) fiber.Handler ``` **配置方式**: ```go // 后台认证中间件 adminAuthMiddleware := middleware.Auth(middleware.AuthConfig{ TokenValidator: adminTokenValidator, // 自定义验证函数 SkipPaths: []string{"/api/admin/login", "/api/admin/refresh-token"}, }) // H5 认证中间件 h5AuthMiddleware := middleware.Auth(middleware.AuthConfig{ TokenValidator: h5TokenValidator, // 自定义验证函数 SkipPaths: []string{"/api/h5/login", "/api/h5/refresh-token"}, }) ``` #### Token 验证器设计 **后台 Token 验证器**: ```go adminTokenValidator := func(token string) (*middleware.UserContextInfo, error) { // 1. 验证 token tokenInfo, err := tokenManager.ValidateAccessToken(ctx, token) if err != nil { return nil, err } // 2. 检查用户类型(后台只允许平台用户和代理账号) allowedTypes := []int{ constants.UserTypeSuperAdmin, // 超级管理员 constants.UserTypePlatform, // 平台用户 constants.UserTypeAgent, // 代理账号 } if !contains(allowedTypes, tokenInfo.UserType) { return nil, errors.New(errors.CodeForbidden, "无权访问后台") } // 3. 返回用户上下文信息 return &middleware.UserContextInfo{ UserID: tokenInfo.UserID, UserType: tokenInfo.UserType, ShopID: tokenInfo.ShopID, EnterpriseID: tokenInfo.EnterpriseID, }, nil } ``` **H5 Token 验证器**: ```go h5TokenValidator := func(token string) (*middleware.UserContextInfo, error) { // 1. 验证 token tokenInfo, err := tokenManager.ValidateAccessToken(ctx, token) if err != nil { return nil, err } // 2. 检查用户类型(H5 只允许代理账号和企业账号) allowedTypes := []int{ constants.UserTypeAgent, // 代理账号 constants.UserTypeEnterprise, // 企业账号 } if !contains(allowedTypes, tokenInfo.UserType) { return nil, errors.New(errors.CodeForbidden, "无权访问 H5 端") } // 3. 返回用户上下文信息 return &middleware.UserContextInfo{ UserID: tokenInfo.UserID, UserType: tokenInfo.UserType, ShopID: tokenInfo.ShopID, EnterpriseID: tokenInfo.EnterpriseID, }, nil } ``` #### 中间件执行流程 ``` HTTP 请求 │ ├─ 1. Auth 中间件(pkg/middleware/auth.go) │ ├─ 检查路径是否在 SkipPaths 中 │ │ ├─ 是 → 跳过认证,执行下一个中间件 │ │ └─ 否 → 继续认证流程 │ │ │ ├─ 提取 token(从 Authorization header) │ │ ├─ 如果缺失 → 返回 CodeMissingToken (401) │ │ └─ 提取 "Bearer {token}" │ │ │ ├─ 调用 TokenValidator 函数 │ │ ├─ 验证 token(查询 Redis) │ │ ├─ 检查用户类型权限 │ │ └─ 返回 UserContextInfo │ │ │ ├─ 将用户信息注入 context │ │ ├─ c.Locals(ContextKeyUserID, userInfo.UserID) │ │ ├─ c.Locals(ContextKeyUserType, userInfo.UserType) │ │ ├─ c.Locals(ContextKeyShopID, userInfo.ShopID) │ │ ├─ c.Locals(ContextKeyEnterpriseID, userInfo.EnterpriseID) │ │ └─ c.SetUserContext(ctx) // 用于 GORM 数据权限过滤 │ │ │ └─ 执行下一个中间件 (c.Next()) │ ├─ 2. Permission 中间件(可选,pkg/middleware/permission.go) │ ├─ 从 context 获取 userID │ ├─ 检查权限码(如 "user:create") │ └─ 如果无权限 → 返回 CodeForbidden (403) │ └─ 3. 业务处理器(Handler) ├─ 从 context 获取用户信息 └─ 执行业务逻辑 ``` --- ### 2.4 路由设计 #### 后台路由(/api/admin) ```go // 公开路由(无需认证) public := api.Group("/admin") public.Post("/login", authHandler.Login) // 登录 public.Post("/refresh-token", authHandler.RefreshToken) // 刷新 token // 受保护路由(需要认证) protected := api.Group("/admin") protected.Use(adminAuthMiddleware) // 应用认证中间件 protected.Post("/logout", authHandler.Logout) // 登出 protected.Get("/me", authHandler.GetMe) // 获取当前用户 protected.Put("/password", authHandler.ChangePassword) // 修改密码 // 其他受保护路由(业务模块) protected.Get("/accounts", accountHandler.List) // 账号管理 protected.Get("/roles", roleHandler.List) // 角色管理 protected.Get("/permissions", permissionHandler.List) // 权限管理 // ... ``` #### H5 路由(/api/h5) ```go // 公开路由(无需认证) public := api.Group("/h5") public.Post("/login", authHandler.Login) // 登录 public.Post("/refresh-token", authHandler.RefreshToken) // 刷新 token // 受保护路由(需要认证) protected := api.Group("/h5") protected.Use(h5AuthMiddleware) // 应用认证中间件 protected.Post("/logout", authHandler.Logout) // 登出 protected.Get("/me", authHandler.GetMe) // 获取当前用户 protected.Put("/password", authHandler.ChangePassword) // 修改密码 // H5 业务路由 protected.Get("/shops", shopHandler.List) // 店铺列表 protected.Get("/enterprises", enterpriseHandler.List) // 企业列表 // ... ``` #### 个人客户路由(/api/c) ```go // 公开路由(无需认证) public := api.Group("/c/v1") public.Post("/login/send-code", personalCustomerHandler.SendCode) // 发送验证码 public.Post("/login", personalCustomerHandler.Login) // 登录 // 受保护路由(需要认证) protected := api.Group("/c/v1") protected.Use(personalAuthMiddleware) // 应用个人客户认证中间件 protected.Get("/profile", personalCustomerHandler.GetProfile) // 获取个人资料 protected.Put("/profile", personalCustomerHandler.UpdateProfile) // 更新个人资料 // ... ``` **路由层级关系**: ``` /api ├── /admin (后台,adminAuthMiddleware) │ ├── /login (公开) │ ├── /logout (受保护) │ └── ... ├── /h5 (H5 端,h5AuthMiddleware) │ ├── /login (公开) │ ├── /logout (受保护) │ └── ... └── /c/v1 (个人客户,personalAuthMiddleware) ├── /login (公开) ├── /profile (受保护) └── ... ``` --- ## 3. 数据模型设计 ### 3.1 DTO 设计 #### 登录请求(LoginRequest) ```go type LoginRequest struct { Username string `json:"username" validate:"required" description:"用户名或手机号"` Password string `json:"password" validate:"required" description:"密码"` Device string `json:"device" validate:"omitempty,oneof=web h5 mobile" description:"设备类型"` } ``` #### 登录响应(LoginResponse) ```go type LoginResponse struct { AccessToken string `json:"access_token" description:"访问令牌"` RefreshToken string `json:"refresh_token" description:"刷新令牌"` ExpiresIn int64 `json:"expires_in" description:"访问令牌过期时间(秒)"` User UserInfo `json:"user" description:"用户信息"` Permissions []string `json:"permissions" description:"权限列表"` } type UserInfo struct { ID uint `json:"id"` Username string `json:"username"` Phone string `json:"phone"` UserType int `json:"user_type"` UserTypeName string `json:"user_type_name"` // "超级管理员" / "平台用户" / ... ShopID uint `json:"shop_id,omitempty"` ShopName string `json:"shop_name,omitempty"` EnterpriseID uint `json:"enterprise_id,omitempty"` EnterpriseName string `json:"enterprise_name,omitempty"` } ``` #### 刷新 Token 请求(RefreshTokenRequest) ```go type RefreshTokenRequest struct { RefreshToken string `json:"refresh_token" validate:"required" description:"刷新令牌"` } ``` #### 刷新 Token 响应(RefreshTokenResponse) ```go type RefreshTokenResponse struct { AccessToken string `json:"access_token" description:"新的访问令牌"` ExpiresIn int64 `json:"expires_in" description:"过期时间(秒)"` } ``` #### 修改密码请求(ChangePasswordRequest) ```go type ChangePasswordRequest struct { OldPassword string `json:"old_password" validate:"required" description:"旧密码"` NewPassword string `json:"new_password" validate:"required,min=8,max=32" description:"新密码(8-32位)"` } ``` ### 3.2 统一响应格式 所有 API 响应使用 `pkg/response` 的统一格式: ```json { "code": 0, "msg": "成功", "data": { "access_token": "550e8400-e29b-41d4-a716-446655440000", "refresh_token": "660e8400-e29b-41d4-a716-446655440001", "expires_in": 86400, "user": { "id": 123, "username": "admin", "phone": "13800000000", "user_type": 2, "user_type_name": "平台用户" }, "permissions": ["user:create", "user:update", "user:delete"] }, "timestamp": "2026-01-15T12:00:00Z" } ``` **错误响应**: ```json { "code": 1010, "msg": "用户名或密码错误", "data": null, "timestamp": "2026-01-15T12:00:00Z" } ``` --- ## 4. 安全设计 ### 4.1 密码安全 | 机制 | 实现方式 | 说明 | |------|---------|------| | **密码哈希** | bcrypt (cost=10) | 慢哈希算法,防暴力破解 | | **密码复杂度** | 8-32 位,字母+数字 | Validator 验证 | | **密码存储** | 不返回给客户端 | `json:"-"` 标签 | | **密码传输** | HTTPS 加密 | 生产环境强制 HTTPS | ### 4.2 Token 安全 | 机制 | 实现方式 | 说明 | |------|---------|------| | **Token 生成** | UUID v4 | 不可预测,128 位随机 | | **Token 过期** | 24 小时(access)
7 天(refresh) | 配置化 | | **Token 撤销** | Redis 删除 key | 支持立即登出 | | **Token 绑定** | 记录 IP、设备 | 便于审计(后续可加强验证) | ### 4.3 防暴力破解 | 机制 | 实现方式 | 说明 | |------|---------|------| | **限流** | 集成 `pkg/middleware/ratelimit.go` | 同一 IP 每分钟最多 10 次登录尝试 | | **错误消息** | 统一返回"用户名或密码错误" | 防止用户枚举攻击 | | **账号锁定** | 后续迭代 | 5 次失败锁定 15 分钟 | ### 4.4 HTTPS 强制 **生产环境**: - 配置 Fiber HTTPS - 使用 Let's Encrypt 自动签发证书 - 重定向 HTTP → HTTPS **开发环境**: - 允许 HTTP - 使用自签名证书测试 --- ## 5. 性能优化 ### 5.1 Redis 连接池 ```go redis.Options{ Addr: "localhost:6379", PoolSize: 100, // 连接池大小 MinIdleConns: 10, // 最小空闲连接 MaxRetries: 3, // 重试次数 DialTimeout: 5 * time.Second, ReadTimeout: 3 * time.Second, WriteTimeout: 3 * time.Second, } ``` ### 5.2 Token 验证缓存 **优化策略**: - ✅ Redis 查询已经很快(< 5ms) - ❌ 不再添加本地缓存(避免分布式一致性问题) - ✅ 使用 Redis Pipeline 批量操作(撤销多个 token) ### 5.3 权限查询优化 **问题**:每次登录都查询用户权限,涉及多表 JOIN **优化方案**: 1. 查询用户的所有角色(`account_role` 表) 2. 批量查询角色的权限(`role_permission` 表,使用 `IN` 查询) 3. 去重权限编码 4. 缓存到 Redis(可选,5 分钟 TTL) **代码示例**: ```go // 1. 查询用户角色 roleIDs, err := accountRoleStore.GetRoleIDsByAccountID(ctx, userID) // 2. 批量查询权限 permissions, err := permissionStore.GetByRoleIDs(ctx, roleIDs) // 3. 提取权限编码 permCodes := make([]string, 0, len(permissions)) for _, perm := range permissions { permCodes = append(permCodes, perm.PermCode) } return permCodes, nil ``` --- ## 6. 错误处理 ### 6.1 错误码扩展 ```go // pkg/errors/codes.go // 认证相关错误码 CodeMissingToken = 1002 // 缺失认证令牌 CodeInvalidToken = 1003 // 无效或过期的令牌 CodeUnauthorized = 1004 // 未授权 CodeForbidden = 1005 // 禁止访问 // 登录相关错误码(新增) CodeInvalidCredentials = 1010 // 用户名或密码错误 CodeAccountDisabled = 1011 // 账号已禁用 CodeAccountLocked = 1012 // 账号已锁定 CodePasswordExpired = 1013 // 密码已过期 CodeInvalidOldPassword = 1014 // 旧密码错误 CodeInvalidPassword = 1015 // 密码格式不正确(已存在) CodePasswordTooWeak = 1016 // 密码强度不足(已存在) ``` ### 6.2 错误处理流程 ``` 业务层错误 │ ├─ 返回 AppError(errors.New(code, message)) │ ↓ Handler 层接收错误 │ ├─ 直接返回 error(由全局 ErrorHandler 处理) │ ↓ 全局 ErrorHandler │ ├─ 提取错误码和消息 ├─ 生成统一 JSON 响应 ├─ 设置 HTTP 状态码 ├─ 记录日志 └─ 返回给客户端 ``` --- ## 7. 监控和日志 ### 7.1 日志记录 **登录成功**: ```go logger.Info("用户登录成功", zap.Uint("user_id", userID), zap.String("username", username), zap.String("device", device), zap.String("ip", clientIP), ) ``` **登录失败**: ```go logger.Warn("用户登录失败", zap.String("username", username), zap.String("reason", "密码错误"), zap.String("ip", clientIP), ) ``` **Token 验证失败**: ```go logger.Warn("Token 验证失败", zap.String("token", token[:10]+"..."), // 只记录前 10 位 zap.String("reason", "已过期"), zap.String("ip", clientIP), ) ``` ### 7.2 监控指标 **关键指标**: - 登录成功率 - 登录失败率(按原因分类) - Token 验证耗时(P50、P95、P99) - Redis 连接错误次数 - 并发登录数 **告警规则**: - 登录失败率 > 30%(可能是暴力破解) - Token 验证耗时 P95 > 10ms - Redis 连接错误次数 > 10 次/分钟 --- ## 8. 测试策略 ### 8.1 单元测试 **覆盖模块**: - Token 管理器(`pkg/auth/token_test.go`) - 认证服务(`internal/service/auth/service_test.go`) **测试方法**: - 使用 Mock 对象(`github.com/stretchr/testify/mock`) - Mock `AccountStore`、`Redis` - 覆盖率目标:≥ 90% ### 8.2 集成测试 **覆盖接口**: - 后台登录、登出、刷新 token - H5 登录、登出、刷新 token - 认证中间件行为 **测试环境**: - 使用 `testcontainers` 启动真实 PostgreSQL 和 Redis - 测试完整的请求-响应流程 - 验证 Redis 数据存储正确 ### 8.3 性能测试 **测试场景**: - Token 验证性能(目标:< 5ms) - 登录性能(目标:< 200ms) - 并发登录(1000 并发) **工具**: - Go Benchmark(`go test -bench`) - Apache Bench(`ab`) - Vegeta(负载测试) --- ## 9. 部署和运维 ### 9.1 环境配置 **开发环境**(`configs/config.dev.yaml`): ```yaml jwt: secret_key: "dev-secret-key-32-characters-long" access_token_ttl: 24h refresh_token_ttl: 168h # 7 days redis: address: "localhost:6379" password: "" db: 0 ``` **生产环境**(`configs/config.prod.yaml`): ```yaml jwt: secret_key: "${JWT_SECRET_KEY}" # 从环境变量读取 access_token_ttl: 24h refresh_token_ttl: 168h redis: address: "${REDIS_ADDR}" password: "${REDIS_PASSWORD}" db: 0 ``` ### 9.2 Redis 高可用 **生产环境推荐**: - 使用 Redis 哨兵模式(Sentinel)或集群模式(Cluster) - 配置主从复制 - 定期备份(RDB + AOF) **配置示例**: ```yaml redis: mode: sentinel # sentinel / cluster / standalone master_name: "mymaster" sentinel_addrs: - "sentinel1:26379" - "sentinel2:26379" - "sentinel3:26379" ``` ### 9.3 健康检查 **API 健康检查**: ``` GET /health ``` **响应**: ```json { "status": "ok", "redis": "connected", "postgres": "connected" } ``` --- ## 10. 后续优化方向 1. **Token Rotation**:刷新 token 时同时更新 refresh token 2. **设备指纹**:绑定 token 到设备,防止 token 被盗用 3. **IP 白名单**:限制特定 IP 访问 4. **账号锁定策略**:登录失败 5 次锁定 15 分钟 5. **两步验证(2FA)**:短信验证码、TOTP 6. **单点登录(SSO)**:统一登录入口 7. **审计日志**:记录登录、权限变更等操作 8. **密码策略**:强制定期修改、密码历史记录 9. **OAuth 第三方登录**:微信企业登录、钉钉登录 10. **实时踢人**:管理员强制下线用户 --- **文档状态**: 待审批 **创建时间**: 2026-01-15 **最后更新**: 2026-01-15