feat: 实现 C 端完整认证系统(client-auth-system)
实现面向个人客户的 7 个认证接口(A1-A7),覆盖资产验证、 微信公众号/小程序登录、手机号绑定/换绑、退出登录完整流程。 主要变更: - 新增 PersonalCustomerOpenID 模型,支持多 AppID 多 OpenID 管理 - 实现有状态 JWT(JWT + Redis 双重校验),支持服务端主动失效 - 扩展微信 SDK:小程序 Code2Session + 3 个 DB 动态工厂函数 - 实现 A1 资产验证 IP 限流(30/min)和 A4 三层验证码限流 - 新增 7 个错误码(1180-1186)和 6 个 Redis Key 函数 - 注册 /api/c/v1/auth/* 下 7 个端点并更新 OpenAPI 文档 - 数据库迁移 000083:新建 tb_personal_customer_openid 表
This commit is contained in:
@@ -1,32 +1,37 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auth"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// PersonalAuthMiddleware 个人客户认证中间件
|
||||
type PersonalAuthMiddleware struct {
|
||||
jwtManager *auth.JWTManager
|
||||
redis *redis.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewPersonalAuthMiddleware 创建个人客户认证中间件
|
||||
func NewPersonalAuthMiddleware(jwtManager *auth.JWTManager, logger *zap.Logger) *PersonalAuthMiddleware {
|
||||
func NewPersonalAuthMiddleware(jwtManager *auth.JWTManager, rdb *redis.Client, logger *zap.Logger) *PersonalAuthMiddleware {
|
||||
return &PersonalAuthMiddleware{
|
||||
jwtManager: jwtManager,
|
||||
redis: rdb,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate 认证中间件
|
||||
// JWT + Redis 双重校验:先验证 JWT 签名和有效期,再检查 Redis 中 token 是否存在
|
||||
func (m *PersonalAuthMiddleware) Authenticate() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
// 从 Authorization header 获取 token
|
||||
authHeader := c.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
m.logger.Warn("个人客户认证失败:缺少 Authorization header",
|
||||
@@ -36,7 +41,6 @@ func (m *PersonalAuthMiddleware) Authenticate() fiber.Handler {
|
||||
return errors.New(errors.CodeUnauthorized, "未提供认证令牌")
|
||||
}
|
||||
|
||||
// 检查 Bearer 前缀
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
m.logger.Warn("个人客户认证失败:Authorization header 格式错误",
|
||||
@@ -48,7 +52,6 @@ func (m *PersonalAuthMiddleware) Authenticate() fiber.Handler {
|
||||
|
||||
token := parts[1]
|
||||
|
||||
// 验证 token
|
||||
claims, err := m.jwtManager.VerifyPersonalCustomerToken(token)
|
||||
if err != nil {
|
||||
m.logger.Warn("个人客户认证失败:token 验证失败",
|
||||
@@ -58,12 +61,35 @@ func (m *PersonalAuthMiddleware) Authenticate() fiber.Handler {
|
||||
return errors.New(errors.CodeUnauthorized, "认证令牌无效或已过期")
|
||||
}
|
||||
|
||||
// 将客户信息存储到 context 中
|
||||
// Redis 有效性检查:token 必须在 Redis 中存在才视为有效
|
||||
// 支持服务端主动失效(封禁/强制下线/退出登录)
|
||||
redisKey := constants.RedisPersonalCustomerTokenKey(claims.CustomerID)
|
||||
storedToken, redisErr := m.redis.Get(context.Background(), redisKey).Result()
|
||||
if redisErr == redis.Nil {
|
||||
m.logger.Warn("个人客户认证失败:token 已被服务端失效",
|
||||
zap.Uint("customer_id", claims.CustomerID),
|
||||
zap.String("path", c.Path()),
|
||||
)
|
||||
return errors.New(errors.CodeUnauthorized, "认证令牌已失效,请重新登录")
|
||||
}
|
||||
if redisErr != nil {
|
||||
m.logger.Error("个人客户认证:Redis 查询异常",
|
||||
zap.Uint("customer_id", claims.CustomerID),
|
||||
zap.Error(redisErr),
|
||||
)
|
||||
return errors.New(errors.CodeUnauthorized, "认证服务异常,请稍后重试")
|
||||
}
|
||||
// 比对 Redis 中存储的 token 与当前请求 token 是否一致
|
||||
if storedToken != token {
|
||||
m.logger.Warn("个人客户认证失败:token 不匹配(可能已在其他设备登录)",
|
||||
zap.Uint("customer_id", claims.CustomerID),
|
||||
zap.String("path", c.Path()),
|
||||
)
|
||||
return errors.New(errors.CodeUnauthorized, "认证令牌已失效,请重新登录")
|
||||
}
|
||||
|
||||
c.Locals("customer_id", claims.CustomerID)
|
||||
c.Locals("customer_phone", claims.Phone)
|
||||
|
||||
// 设置 SkipOwnerFilter 标记,跳过 B 端数据权限过滤
|
||||
// 个人客户不参与 RBAC 权限体系,不需要 Owner 过滤
|
||||
c.Locals("skip_owner_filter", true)
|
||||
|
||||
m.logger.Debug("个人客户认证成功",
|
||||
|
||||
Reference in New Issue
Block a user