package middleware import ( "net/http" "strings" "github.com/edu-cloud/api-gateway/internal/config" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) // AuthMiddleware 验证 JWT 并注入用户信息到请求头 // P1 用 HS256,P2 改 RS256(IAM 签发) func AuthMiddleware(cfg *config.Config) gin.HandlerFunc { return func(c *gin.Context) { // 健康检查跳过鉴权 if c.Request.URL.Path == "/healthz" { c.Next() return } authHeader := c.GetHeader("Authorization") if authHeader == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "success": false, "error": gin.H{ "code": "UNAUTHORIZED", "message": "missing authorization header", }, }) return } tokenStr := strings.TrimPrefix(authHeader, "Bearer ") if tokenStr == authHeader { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "success": false, "error": gin.H{ "code": "UNAUTHORIZED", "message": "invalid authorization scheme, expected Bearer", }, }) return } token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(cfg.JWTSecret), nil }) if err != nil || !token.Valid { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "success": false, "error": gin.H{ "code": "INVALID_TOKEN", "message": "token validation failed", }, }) return } claims, ok := token.Claims.(jwt.MapClaims) if !ok { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "success": false, "error": gin.H{ "code": "INVALID_CLAIMS", "message": "invalid token claims", }, }) return } // 注入用户信息到下游请求头 if sub, ok := claims["sub"].(string); ok { c.Request.Header.Set("x-user-id", sub) } if roles, ok := claims["roles"].([]interface{}); ok { roleStrs := make([]string, 0, len(roles)) for _, r := range roles { if s, ok := r.(string); ok { roleStrs = append(roleStrs, s) } } c.Request.Header.Set("x-user-roles", strings.Join(roleStrs, ",")) } c.Next() } } // RequestIDMiddleware 注入请求 ID 用于全链路追踪 func RequestIDMiddleware() gin.HandlerFunc { return func(c *gin.Context) { requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = generateUUID() } c.Set("request_id", requestID) c.Writer.Header().Set("X-Request-ID", requestID) c.Next() } } // generateUUID 生成带 req- 前缀的唯一请求 ID // 使用 uuid.New() 基于 RFC 4122 v4 随机 UUID,避免 time.Now() 产生的冲突与可预测性 func generateUUID() string { return "req-" + uuid.New().String() }