主要变更: - 新增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模式)
704 lines
22 KiB
Markdown
704 lines
22 KiB
Markdown
# 提案:实现 B 端认证系统
|
||
|
||
**Change ID**: `implement-b-end-auth-system`
|
||
**类型**: 新功能
|
||
**优先级**: 高
|
||
**预计工作量**: 3-5 天
|
||
|
||
---
|
||
|
||
## 概述
|
||
|
||
完成 B 端(Web 后台 + H5 端)的完整认证系统,包括后台管理员登录、代理商登录、企业用户登录,以及配套的 token 管理、登出、刷新等功能。
|
||
|
||
## 背景
|
||
|
||
### 当前状态
|
||
|
||
项目已完成:
|
||
- ✅ C 端(个人客户)JWT 认证
|
||
- ✅ 通用认证中间件框架 (`pkg/middleware/auth.go`)
|
||
- ✅ RBAC 权限体系(角色、权限、数据权限过滤)
|
||
- ✅ 用户上下文传递机制
|
||
- ✅ 密码加密(bcrypt)
|
||
|
||
缺失功能:
|
||
- ❌ B 端登录接口(后台/代理/企业)
|
||
- ❌ B 端 token 生成和 Redis 存储
|
||
- ❌ 登出功能(token 撤销)
|
||
- ❌ Token 刷新机制
|
||
- ❌ 多端认证中间件配置
|
||
|
||
### 用户需求
|
||
|
||
用户明确要求:
|
||
> "目前不需要做个人用户登录,只需要做后台代理商/平台登录,h5端代理/企业用户登录"
|
||
|
||
需要支持:
|
||
1. **Web 后台登录**:平台管理员、代理商账号
|
||
2. **H5 端登录**:代理商账号、企业账号
|
||
|
||
## 目标
|
||
|
||
### 业务目标
|
||
|
||
1. 实现后台管理员、代理商、企业用户的账号密码登录
|
||
2. 支持多端(Web 后台、H5)分别认证
|
||
3. 提供完整的 token 生命周期管理(生成、验证、刷新、撤销)
|
||
4. 与现有 RBAC 权限体系无缝集成
|
||
5. 保持与 C 端认证的架构一致性
|
||
|
||
### 技术目标
|
||
|
||
1. 复用现有认证中间件框架
|
||
2. 遵循项目分层架构(Handler → Service → Store → Model)
|
||
3. 统一错误处理和响应格式
|
||
4. 所有 API 响应时间 < 200ms(P95)
|
||
5. Token 验证缓存在 Redis,支持高并发
|
||
|
||
## 设计决策
|
||
|
||
### 1. 认证方式选择
|
||
|
||
**决策**:B 端使用 **Redis Token** 认证,而非 JWT
|
||
|
||
**理由**:
|
||
- ✅ **可撤销性**:支持立即登出和强制下线
|
||
- ✅ **灵活性**:可存储额外会话信息(登录时间、设备信息等)
|
||
- ✅ **安全性**:Token 可以是随机 UUID,不携带敏感信息
|
||
- ✅ **分布式友好**:Redis 集群天然支持多服务器部署
|
||
- ✅ **与现有架构一致**:项目已使用 Redis 存储 token(`pkg/validator/token.go`)
|
||
|
||
**对比 JWT**:
|
||
- ❌ JWT 无法撤销(除非维护黑名单,失去无状态优势)
|
||
- ❌ JWT payload 可见(Base64 解码即可查看)
|
||
- ❌ 不适合需要频繁撤销的场景(后台管理系统)
|
||
|
||
### 2. Token 存储结构
|
||
|
||
**Redis Key 设计**:
|
||
```
|
||
auth:token:{token} → 用户基本信息(JSON)
|
||
auth:user:{userID}:tokens → 用户的所有 token 列表(Set)
|
||
```
|
||
|
||
**存储内容**:
|
||
```json
|
||
{
|
||
"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"
|
||
}
|
||
```
|
||
|
||
**TTL 配置**:
|
||
- Access Token:24 小时(可配置)
|
||
- Refresh Token:7 天(可配置)
|
||
|
||
### 3. 多端认证设计
|
||
|
||
**Web 后台**:
|
||
- 路由前缀:`/api/admin/*`
|
||
- 认证方式:Bearer Token
|
||
- 权限过滤:`platform = 'web' OR platform = 'all'`
|
||
- 支持用户类型:超级管理员、平台用户、代理账号
|
||
|
||
**H5 端**:
|
||
- 路由前缀:`/api/h5/*`
|
||
- 认证方式:Bearer Token
|
||
- 权限过滤:`platform = 'h5' OR platform = 'all'`
|
||
- 支持用户类型:代理账号、企业账号
|
||
|
||
### 4. 登录流程设计
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ POST /api/admin/login │
|
||
│ POST /api/h5/login │
|
||
└────────────────────────────┬────────────────────────────────┘
|
||
│
|
||
┌──────────▼──────────┐
|
||
│ 1. 验证用户名/密码 │
|
||
│ (bcrypt.Compare)│
|
||
└──────────┬──────────┘
|
||
│
|
||
┌──────────▼──────────┐
|
||
│ 2. 检查账号状态 │
|
||
│ (status=1) │
|
||
└──────────┬──────────┘
|
||
│
|
||
┌──────────▼──────────┐
|
||
│ 3. 生成 UUID Token │
|
||
│ (uuid.New()) │
|
||
└──────────┬──────────┘
|
||
│
|
||
┌──────────▼──────────┐
|
||
│ 4. 存储到 Redis │
|
||
│ (TTL: 24h) │
|
||
└──────────┬──────────┘
|
||
│
|
||
┌──────────▼──────────┐
|
||
│ 5. 返回 Token │
|
||
│ (+ 用户信息) │
|
||
└──────────┬──────────┘
|
||
│
|
||
┌────────────────────────────▼────────────────────────────────┐
|
||
│ Response: {token, refresh_token, user_info, permissions} │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 5. 权限检查流程
|
||
|
||
```
|
||
请求 → Auth 中间件 → Permission 中间件 → 业务处理器
|
||
↓ ↓
|
||
验证 Token 检查权限码
|
||
↓ ↓
|
||
设置用户上下文 验证角色权限
|
||
```
|
||
|
||
## 范围
|
||
|
||
### 包含功能
|
||
|
||
#### 核心功能
|
||
1. **登录接口**
|
||
- `POST /api/admin/login`:后台登录(平台用户、代理账号)
|
||
- `POST /api/h5/login`:H5 端登录(代理账号、企业账号)
|
||
- 验证用户名/密码
|
||
- 生成 access_token 和 refresh_token
|
||
- 返回用户信息和权限列表
|
||
|
||
2. **登出接口**
|
||
- `POST /api/admin/logout`:后台登出
|
||
- `POST /api/h5/logout`:H5 端登出
|
||
- 撤销 access_token
|
||
- 撤销 refresh_token
|
||
- 清理 Redis 缓存
|
||
|
||
3. **Token 刷新接口**
|
||
- `POST /api/admin/refresh-token`:后台刷新 token
|
||
- `POST /api/h5/refresh-token`:H5 端刷新 token
|
||
- 验证 refresh_token
|
||
- 生成新的 access_token
|
||
- 可选:刷新 refresh_token(rotation)
|
||
|
||
4. **认证中间件配置**
|
||
- Web 后台认证中间件
|
||
- H5 端认证中间件
|
||
- 统一使用 `pkg/middleware/auth.go` 的 `Auth()` 函数
|
||
- 配置不同的 token 验证器
|
||
|
||
5. **Token 管理服务**
|
||
- Token 生成(access + refresh)
|
||
- Token 验证(从 Redis 查询)
|
||
- Token 撤销(删除 Redis key)
|
||
- Token 续期(更新 TTL)
|
||
- 用户所有 token 查询和批量撤销
|
||
|
||
#### 辅助功能
|
||
6. **获取当前用户信息**
|
||
- `GET /api/admin/me`:后台当前用户
|
||
- `GET /api/h5/me`:H5 当前用户
|
||
- 返回用户信息、角色、权限列表
|
||
|
||
7. **修改当前用户密码**
|
||
- `PUT /api/admin/password`:后台修改密码
|
||
- `PUT /api/h5/password`:H5 修改密码
|
||
- 验证旧密码
|
||
- 更新密码(bcrypt 哈希)
|
||
- 撤销所有旧 token
|
||
|
||
### 不包含功能
|
||
|
||
- ❌ 找回密码(通过邮件/短信)→ 后续迭代
|
||
- ❌ 两步验证(2FA)→ 后续迭代
|
||
- ❌ 单点登录(SSO)→ 后续迭代
|
||
- ❌ OAuth 第三方登录(微信、钉钉等)→ 后续迭代
|
||
- ❌ 设备管理和多设备限制 → 后续迭代
|
||
- ❌ 登录历史和审计日志 → 后续迭代
|
||
|
||
## 技术方案
|
||
|
||
### 1. 目录结构
|
||
|
||
```
|
||
internal/
|
||
├── handler/
|
||
│ ├── admin/
|
||
│ │ └── auth.go # 后台认证 Handler(新增)
|
||
│ └── h5/
|
||
│ └── auth.go # H5 认证 Handler(新增)
|
||
├── service/
|
||
│ └── auth/
|
||
│ └── service.go # 认证服务(新增)
|
||
├── store/
|
||
│ └── postgres/
|
||
│ └── account_store.go # 账号查询(已存在,扩展方法)
|
||
├── model/
|
||
│ └── auth_dto.go # 认证 DTO(新增)
|
||
pkg/
|
||
├── auth/
|
||
│ └── token.go # Token 管理工具(新增)
|
||
├── constants/
|
||
│ └── auth.go # 认证常量(新增)
|
||
└── middleware/
|
||
└── auth.go # 通用认证中间件(已存在,无需修改)
|
||
```
|
||
|
||
### 2. 核心模块设计
|
||
|
||
#### 2.1 Token 管理器(pkg/auth/token.go)
|
||
|
||
```go
|
||
package auth
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
"github.com/google/uuid"
|
||
"github.com/redis/go-redis/v9"
|
||
)
|
||
|
||
// TokenManager Token 管理器
|
||
type TokenManager struct {
|
||
rdb *redis.Client
|
||
accessTokenTTL time.Duration
|
||
refreshTokenTTL time.Duration
|
||
}
|
||
|
||
// TokenInfo Token 信息(存储在 Redis)
|
||
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"`
|
||
}
|
||
|
||
// GenerateTokenPair 生成 access token 和 refresh token
|
||
func (m *TokenManager) GenerateTokenPair(ctx context.Context, info *TokenInfo) (accessToken, refreshToken string, err error)
|
||
|
||
// ValidateAccessToken 验证 access token 并返回用户信息
|
||
func (m *TokenManager) ValidateAccessToken(ctx context.Context, token string) (*TokenInfo, error)
|
||
|
||
// ValidateRefreshToken 验证 refresh token
|
||
func (m *TokenManager) ValidateRefreshToken(ctx context.Context, token string) (*TokenInfo, error)
|
||
|
||
// RefreshAccessToken 使用 refresh token 刷新 access token
|
||
func (m *TokenManager) RefreshAccessToken(ctx context.Context, refreshToken string) (newAccessToken string, err error)
|
||
|
||
// RevokeToken 撤销单个 token
|
||
func (m *TokenManager) RevokeToken(ctx context.Context, token string) error
|
||
|
||
// RevokeAllUserTokens 撤销用户的所有 token
|
||
func (m *TokenManager) RevokeAllUserTokens(ctx context.Context, userID uint) error
|
||
|
||
// RenewTokenTTL 续期 token(用于"记住我"功能)
|
||
func (m *TokenManager) RenewTokenTTL(ctx context.Context, token string, ttl time.Duration) error
|
||
```
|
||
|
||
#### 2.2 认证服务(internal/service/auth/service.go)
|
||
|
||
```go
|
||
package auth
|
||
|
||
import (
|
||
"context"
|
||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||
"github.com/break/junhong_cmp_fiber/pkg/auth"
|
||
"golang.org/x/crypto/bcrypt"
|
||
)
|
||
|
||
// Service 认证服务
|
||
type Service struct {
|
||
accountStore AccountStore
|
||
tokenManager *auth.TokenManager
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// LoginRequest 登录请求
|
||
type LoginRequest struct {
|
||
Username string `json:"username" validate:"required"`
|
||
Password string `json:"password" validate:"required"`
|
||
Device string `json:"device"` // web / h5 / mobile
|
||
}
|
||
|
||
// LoginResponse 登录响应
|
||
type LoginResponse struct {
|
||
AccessToken string `json:"access_token"`
|
||
RefreshToken string `json:"refresh_token"`
|
||
User *model.Account `json:"user"`
|
||
Permissions []string `json:"permissions"`
|
||
}
|
||
|
||
// Login 用户登录
|
||
func (s *Service) Login(ctx context.Context, req *LoginRequest, clientIP string) (*LoginResponse, error)
|
||
|
||
// Logout 用户登出
|
||
func (s *Service) Logout(ctx context.Context, token string) error
|
||
|
||
// RefreshToken 刷新 token
|
||
func (s *Service) RefreshToken(ctx context.Context, refreshToken string) (newAccessToken string, error)
|
||
|
||
// GetCurrentUser 获取当前用户信息
|
||
func (s *Service) GetCurrentUser(ctx context.Context, userID uint) (*model.Account, []string, error)
|
||
|
||
// ChangePassword 修改密码
|
||
func (s *Service) ChangePassword(ctx context.Context, userID uint, oldPassword, newPassword string) error
|
||
```
|
||
|
||
#### 2.3 认证 Handler(internal/handler/admin/auth.go)
|
||
|
||
```go
|
||
package admin
|
||
|
||
import (
|
||
"github.com/gofiber/fiber/v2"
|
||
"github.com/break/junhong_cmp_fiber/internal/service/auth"
|
||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||
)
|
||
|
||
// AuthHandler 认证处理器
|
||
type AuthHandler struct {
|
||
authService *auth.Service
|
||
}
|
||
|
||
// Login 登录
|
||
// POST /api/admin/login
|
||
func (h *AuthHandler) Login(c *fiber.Ctx) error
|
||
|
||
// Logout 登出
|
||
// POST /api/admin/logout
|
||
func (h *AuthHandler) Logout(c *fiber.Ctx) error
|
||
|
||
// RefreshToken 刷新 token
|
||
// POST /api/admin/refresh-token
|
||
func (h *AuthHandler) RefreshToken(c *fiber.Ctx) error
|
||
|
||
// GetMe 获取当前用户信息
|
||
// GET /api/admin/me
|
||
func (h *AuthHandler) GetMe(c *fiber.Ctx) error
|
||
|
||
// ChangePassword 修改密码
|
||
// PUT /api/admin/password
|
||
func (h *AuthHandler) ChangePassword(c *fiber.Ctx) error
|
||
```
|
||
|
||
### 3. 路由配置
|
||
|
||
```go
|
||
// internal/routes/admin.go
|
||
|
||
// 公开路由(无需认证)
|
||
public := api.Group("/admin")
|
||
public.Post("/login", authHandler.Login)
|
||
public.Post("/refresh-token", authHandler.RefreshToken)
|
||
|
||
// 受保护路由(需要认证)
|
||
protected := api.Group("/admin")
|
||
protected.Use(adminAuthMiddleware) // 使用后台认证中间件
|
||
protected.Post("/logout", authHandler.Logout)
|
||
protected.Get("/me", authHandler.GetMe)
|
||
protected.Put("/password", authHandler.ChangePassword)
|
||
|
||
// ... 其他受保护路由
|
||
```
|
||
|
||
### 4. 中间件配置
|
||
|
||
```go
|
||
// internal/bootstrap/middlewares.go
|
||
|
||
// 后台认证中间件
|
||
adminAuthMiddleware := middleware.Auth(middleware.AuthConfig{
|
||
TokenValidator: func(token string) (*middleware.UserContextInfo, error) {
|
||
tokenInfo, err := tokenManager.ValidateAccessToken(ctx, token)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 检查用户类型(后台只允许平台用户和代理账号)
|
||
if tokenInfo.UserType != constants.UserTypeSuperAdmin &&
|
||
tokenInfo.UserType != constants.UserTypePlatform &&
|
||
tokenInfo.UserType != constants.UserTypeAgent {
|
||
return nil, errors.New(errors.CodeForbidden, "无权访问后台")
|
||
}
|
||
|
||
return &middleware.UserContextInfo{
|
||
UserID: tokenInfo.UserID,
|
||
UserType: tokenInfo.UserType,
|
||
ShopID: tokenInfo.ShopID,
|
||
EnterpriseID: tokenInfo.EnterpriseID,
|
||
}, nil
|
||
},
|
||
SkipPaths: []string{"/api/admin/login", "/api/admin/refresh-token"},
|
||
})
|
||
|
||
// H5 认证中间件
|
||
h5AuthMiddleware := middleware.Auth(middleware.AuthConfig{
|
||
TokenValidator: func(token string) (*middleware.UserContextInfo, error) {
|
||
tokenInfo, err := tokenManager.ValidateAccessToken(ctx, token)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 检查用户类型(H5 只允许代理账号和企业账号)
|
||
if tokenInfo.UserType != constants.UserTypeAgent &&
|
||
tokenInfo.UserType != constants.UserTypeEnterprise {
|
||
return nil, errors.New(errors.CodeForbidden, "无权访问 H5 端")
|
||
}
|
||
|
||
return &middleware.UserContextInfo{
|
||
UserID: tokenInfo.UserID,
|
||
UserType: tokenInfo.UserType,
|
||
ShopID: tokenInfo.ShopID,
|
||
EnterpriseID: tokenInfo.EnterpriseID,
|
||
}, nil
|
||
},
|
||
SkipPaths: []string{"/api/h5/login", "/api/h5/refresh-token"},
|
||
})
|
||
```
|
||
|
||
### 5. Redis Key 设计
|
||
|
||
```go
|
||
// pkg/constants/auth.go
|
||
|
||
// RedisAuthTokenKey 生成认证令牌的 Redis 键
|
||
func RedisAuthTokenKey(token string) string {
|
||
return fmt.Sprintf("auth:token:%s", token)
|
||
}
|
||
|
||
// RedisRefreshTokenKey 生成刷新令牌的 Redis 键
|
||
func RedisRefreshTokenKey(token string) string {
|
||
return fmt.Sprintf("auth:refresh:%s", token)
|
||
}
|
||
|
||
// RedisUserTokensKey 生成用户令牌列表的 Redis 键
|
||
func RedisUserTokensKey(userID uint) string {
|
||
return fmt.Sprintf("auth:user:%d:tokens", userID)
|
||
}
|
||
```
|
||
|
||
### 6. 错误码扩展
|
||
|
||
```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 // 密码强度不足(已存在)
|
||
```
|
||
|
||
## 实现计划
|
||
|
||
详见 `tasks.md`
|
||
|
||
## 测试策略
|
||
|
||
### 单元测试
|
||
|
||
1. **Token 管理器测试**(`pkg/auth/token_test.go`)
|
||
- 生成 token 对
|
||
- 验证 access token
|
||
- 验证 refresh token
|
||
- 刷新 token
|
||
- 撤销 token
|
||
- Redis 连接失败处理
|
||
|
||
2. **认证服务测试**(`internal/service/auth/service_test.go`)
|
||
- 登录成功
|
||
- 登录失败(密码错误、账号禁用)
|
||
- 登出
|
||
- 刷新 token
|
||
- 修改密码
|
||
|
||
### 集成测试
|
||
|
||
3. **登录接口测试**(`tests/integration/admin_auth_test.go`)
|
||
- 后台登录成功
|
||
- H5 登录成功
|
||
- 用户名不存在
|
||
- 密码错误
|
||
- 账号禁用
|
||
- 返回 token 和用户信息
|
||
|
||
4. **认证中间件测试**(`tests/integration/admin_auth_middleware_test.go`)
|
||
- 有效 token 访问受保护路由
|
||
- 无效 token 返回 401
|
||
- 缺失 token 返回 401
|
||
- 过期 token 返回 401
|
||
- 用户类型不匹配返回 403
|
||
|
||
5. **Token 刷新测试**(`tests/integration/token_refresh_test.go`)
|
||
- 使用有效 refresh token 刷新
|
||
- 使用无效 refresh token 失败
|
||
- 撤销后的 refresh token 失败
|
||
|
||
6. **登出测试**(`tests/integration/logout_test.go`)
|
||
- 登出后 token 失效
|
||
- 登出后无法访问受保护路由
|
||
|
||
### 性能测试
|
||
|
||
7. **认证性能测试**(`tests/benchmark/auth_bench_test.go`)
|
||
- Token 验证性能(目标:< 5ms)
|
||
- 登录性能(目标:< 200ms)
|
||
- 并发登录测试(1000 并发)
|
||
|
||
### 测试覆盖率目标
|
||
|
||
- 核心业务逻辑:≥ 90%
|
||
- Handler 层:≥ 80%
|
||
- 整体覆盖率:≥ 70%
|
||
|
||
## 风险和缓解
|
||
|
||
### 风险 1:Redis 单点故障导致认证不可用
|
||
|
||
**影响**:Redis 宕机导致所有用户无法登录和认证
|
||
|
||
**缓解措施**:
|
||
- 使用 Redis 哨兵模式或集群模式(生产环境)
|
||
- 实现 Redis 健康检查和自动重连
|
||
- 添加 Circuit Breaker 模式,避免雪崩
|
||
- 日志记录 Redis 连接失败,便于快速排查
|
||
|
||
### 风险 2:Token 泄露导致账号被盗用
|
||
|
||
**影响**:攻击者获取 token 后可以冒充用户
|
||
|
||
**缓解措施**:
|
||
- Token 使用 UUID v4(不可预测)
|
||
- HTTPS 强制加密传输
|
||
- Token 设置合理的过期时间(24 小时)
|
||
- 实现 IP 绑定和设备指纹(后续迭代)
|
||
- 异常登录检测和通知(后续迭代)
|
||
|
||
### 风险 3:暴力破解登录
|
||
|
||
**影响**:攻击者通过暴力破解获取账号密码
|
||
|
||
**缓解措施**:
|
||
- 集成现有的限流中间件(`pkg/middleware/ratelimit.go`)
|
||
- 登录失败次数限制(5 次锁定 15 分钟)
|
||
- 添加图形验证码(后续迭代)
|
||
- 记录登录失败日志,便于审计
|
||
|
||
### 风险 4:密码存储安全
|
||
|
||
**影响**:数据库泄露导致密码被破解
|
||
|
||
**缓解措施**:
|
||
- 已使用 bcrypt 哈希(cost=10)
|
||
- 禁止明文密码传输(HTTPS)
|
||
- 密码复杂度要求(8-32 位,含字母数字)
|
||
- 定期密码过期提醒(后续迭代)
|
||
|
||
### 风险 5:与现有代码集成冲突
|
||
|
||
**影响**:新代码与现有认证逻辑冲突
|
||
|
||
**缓解措施**:
|
||
- 复用现有的 `pkg/middleware/auth.go` 框架
|
||
- 不修改 C 端认证逻辑(`internal/middleware/personal_auth.go`)
|
||
- 充分的集成测试覆盖
|
||
- 代码审查(Code Review)
|
||
|
||
## 依赖
|
||
|
||
### 外部依赖
|
||
|
||
- ✅ Redis:token 存储和验证
|
||
- ✅ PostgreSQL:用户账号存储
|
||
- ✅ bcrypt:密码哈希
|
||
- ✅ UUID:token 生成
|
||
|
||
### 内部依赖
|
||
|
||
- ✅ `pkg/middleware/auth.go`:通用认证中间件
|
||
- ✅ `pkg/errors`:统一错误处理
|
||
- ✅ `pkg/response`:统一响应格式
|
||
- ✅ `pkg/constants`:常量定义
|
||
- ✅ `internal/model/account.go`:账号模型
|
||
- ✅ `internal/store/postgres/account_store.go`:账号数据访问
|
||
|
||
## 文档
|
||
|
||
需要创建的文档:
|
||
|
||
1. **API 文档**(`docs/api/auth.md`)
|
||
- 登录接口说明
|
||
- 登出接口说明
|
||
- Token 刷新接口说明
|
||
- 错误码说明
|
||
- 示例请求和响应
|
||
|
||
2. **使用指南**(`docs/auth-usage-guide.md`)
|
||
- 如何在新路由中集成认证中间件
|
||
- 如何获取当前用户信息
|
||
- 如何撤销用户 token
|
||
- 常见问题(FAQ)
|
||
|
||
3. **架构说明**(`docs/auth-architecture.md`)
|
||
- 认证流程图
|
||
- Token 存储结构
|
||
- 中间件执行顺序
|
||
- 安全机制说明
|
||
|
||
## 验收标准
|
||
|
||
1. ✅ 后台管理员可以使用用户名/密码登录
|
||
2. ✅ H5 代理商/企业用户可以使用用户名/密码登录
|
||
3. ✅ 登录成功返回 access_token、refresh_token 和用户信息
|
||
4. ✅ 受保护的 API 需要携带有效 token 才能访问
|
||
5. ✅ Token 过期或无效时返回 401 错误
|
||
6. ✅ 用户可以登出,登出后 token 立即失效
|
||
7. ✅ 用户可以使用 refresh_token 刷新 access_token
|
||
8. ✅ 用户可以修改密码,修改后所有旧 token 失效
|
||
9. ✅ 不同用户类型只能访问对应端口的 API(后台/H5)
|
||
10. ✅ 所有测试通过,覆盖率达标
|
||
11. ✅ API 响应时间 P95 < 200ms
|
||
12. ✅ 文档完整,便于其他开发者使用
|
||
|
||
## 后续迭代
|
||
|
||
以下功能留待后续迭代:
|
||
|
||
1. **找回密码**:通过邮件/短信发送重置链接
|
||
2. **两步验证(2FA)**:短信验证码、TOTP
|
||
3. **单点登录(SSO)**:统一登录入口
|
||
4. **OAuth 第三方登录**:微信企业登录、钉钉登录
|
||
5. **设备管理**:查看登录设备、强制下线
|
||
6. **登录历史**:记录登录时间、IP、设备
|
||
7. **审计日志**:记录认证授权相关操作
|
||
8. **IP 白名单**:限制特定 IP 访问
|
||
9. **账号锁定策略**:登录失败次数限制
|
||
10. **密码策略**:强制定期修改、密码历史记录
|
||
|
||
---
|
||
|
||
**提案状态**:待审批
|
||
**创建时间**:2026-01-15
|
||
**最后更新**:2026-01-15
|