Files
junhong_cmp_fiber/internal/middleware/personal_auth.go
Break 5f57429fb0
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m28s
直接换货流程
2026-06-03 16:55:32 +08:00

134 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"
pkgmiddleware "github.com/break/junhong_cmp_fiber/pkg/middleware"
"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 {
if c.Method() == fiber.MethodOptions {
return c.SendStatus(fiber.StatusNoContent)
}
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("customer_asset_type", claims.AssetType)
c.Locals("customer_asset_id", claims.AssetID)
c.Locals("skip_owner_filter", true)
c.SetUserContext(pkgmiddleware.SetUserContext(c.UserContext(), &pkgmiddleware.UserContextInfo{
CustomerID: claims.CustomerID,
}))
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
}
// GetCurrentAsset 从 context 中获取当前登录所用的资产类型和 ID
func GetCurrentAsset(c *fiber.Ctx) (assetType string, assetID uint, ok bool) {
assetType, _ = c.Locals("customer_asset_type").(string)
assetID, _ = c.Locals("customer_asset_id").(uint)
ok = assetType != "" && assetID != 0
return
}