package middleware import ( "context" "strings" "github.com/echochat/backend/config" "github.com/echochat/backend/pkg/logs" "github.com/echochat/backend/pkg/utils" "github.com/gin-gonic/gin" "go.uber.org/zap" ) const ( ContextKeyUserID = "user_id" // Context 中存储当前用户 ID 的 Key ContextKeyUsername = "username" // Context 中存储当前用户名的 Key ContextKeyRoles = "roles" // Context 中存储当前用户角色列表的 Key ContextKeyClientType = "client_type" // Context 中存储客户端类型的 Key(frontend / admin) ) // TokenValidator Token 有效性校验接口 // 由 AuthService 实现,中间件通过此接口检查 Token 是否在 Redis 中有效 type TokenValidator interface { ValidateAccessToken(ctx context.Context, userID int64, clientType, token, username string) bool } // JWTAuth JWT 认证中间件(有状态 JWT) // 验证流程:解析 Token → 检查类型 → 校验 Redis 有效性 → 注入用户信息 func JWTAuth(jwtCfg *config.JWTConfig, validator TokenValidator) gin.HandlerFunc { return func(c *gin.Context) { funcName := "middleware.JWTAuth" ctx := c.Request.Context() authHeader := c.GetHeader("Authorization") if authHeader == "" { utils.ResponseUnauthorized(c, "缺少认证信息") c.Abort() return } parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { utils.ResponseUnauthorized(c, "认证格式错误,应为 Bearer {token}") c.Abort() return } tokenStr := parts[1] claims, err := utils.ParseToken(jwtCfg, tokenStr) if err != nil { logs.Warn(ctx, funcName, "Token 验证失败", zap.Error(err), zap.String("ip", c.ClientIP()), ) utils.ResponseUnauthorized(c, "认证已过期或无效,请重新登录") c.Abort() return } if claims.Subject != "access" { utils.ResponseUnauthorized(c, "无效的 Token 类型") c.Abort() return } // 从 Claims 获取 clientType(兼容旧 Token 无该字段的情况) clientType := claims.ClientType if clientType == "" { clientType = "frontend" } // 校验 Token 是否在 Redis 中有效(有状态 JWT 核心逻辑,按 clientType 隔离) if !validator.ValidateAccessToken(ctx, claims.UserID, clientType, tokenStr, claims.Username) { logs.Warn(ctx, funcName, "Token 已失效(已登出或被覆盖)", zap.Int64("user_id", claims.UserID), zap.String("client_type", clientType), zap.String("ip", c.ClientIP()), ) utils.ResponseUnauthorized(c, "认证已失效,请重新登录") c.Abort() return } c.Set(ContextKeyUserID, claims.UserID) c.Set(ContextKeyUsername, claims.Username) c.Set(ContextKeyRoles, claims.Roles) c.Set(ContextKeyClientType, clientType) c.Next() } } // RequireRole 角色权限检查中间件 // 检查当前用户是否拥有指定角色之一(OR 逻辑),需在 JWTAuth 之后使用 func RequireRole(roles ...string) gin.HandlerFunc { return func(c *gin.Context) { funcName := "middleware.RequireRole" ctx := c.Request.Context() userRoles, exists := c.Get(ContextKeyRoles) if !exists { utils.ResponseForbidden(c, "无权限访问") c.Abort() return } roleList, ok := userRoles.([]string) if !ok { utils.ResponseForbidden(c, "无权限访问") c.Abort() return } for _, required := range roles { for _, userRole := range roleList { if userRole == required { c.Next() return } } } userID, _ := c.Get(ContextKeyUserID) logs.Warn(ctx, funcName, "角色权限不足", zap.Any("user_id", userID), zap.Strings("required", roles), zap.Strings("actual", roleList), ) utils.ResponseForbidden(c, "权限不足,需要角色: "+strings.Join(roles, " 或 ")) c.Abort() } } // GetCurrentUserID 从 Gin Context 获取当前登录用户 ID func GetCurrentUserID(c *gin.Context) (int64, bool) { val, exists := c.Get(ContextKeyUserID) if !exists { return 0, false } userID, ok := val.(int64) return userID, ok } // GetCurrentUsername 从 Gin Context 获取当前登录用户名 func GetCurrentUsername(c *gin.Context) (string, bool) { val, exists := c.Get(ContextKeyUsername) if !exists { return "", false } username, ok := val.(string) return username, ok } // GetCurrentClientType 从 Gin Context 获取当前客户端类型(frontend / admin) func GetCurrentClientType(c *gin.Context) string { val, exists := c.Get(ContextKeyClientType) if !exists { return "frontend" } clientType, ok := val.(string) if !ok { return "frontend" } return clientType }