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, 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 { authHeader := c.Get("Authorization") if authHeader == "" { m.logger.Warn("个人客户认证失败:缺少 Authorization header", zap.String("path", c.Path()), zap.String("method", c.Method()), ) return errors.New(errors.CodeUnauthorized, "未提供认证令牌") } parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { m.logger.Warn("个人客户认证失败:Authorization header 格式错误", zap.String("path", c.Path()), zap.String("auth_header", authHeader), ) return errors.New(errors.CodeUnauthorized, "认证令牌格式错误") } token := parts[1] claims, err := m.jwtManager.VerifyPersonalCustomerToken(token) if err != nil { m.logger.Warn("个人客户认证失败:token 验证失败", zap.String("path", c.Path()), zap.Error(err), ) return errors.New(errors.CodeUnauthorized, "认证令牌无效或已过期") } // 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) c.Locals("skip_owner_filter", true) m.logger.Debug("个人客户认证成功", zap.Uint("customer_id", claims.CustomerID), zap.String("phone", claims.Phone), zap.String("path", c.Path()), ) return c.Next() } } // GetCustomerID 从 context 中获取当前个人客户 ID func GetCustomerID(c *fiber.Ctx) (uint, bool) { customerID, ok := c.Locals("customer_id").(uint) return customerID, ok } // GetCustomerPhone 从 context 中获取当前个人客户手机号 func GetCustomerPhone(c *fiber.Ctx) (string, bool) { phone, ok := c.Locals("customer_phone").(string) return phone, ok }