Go语言Gin框架中间件设计:JWT认证与限流熔断实现方案

微服务架构中,API接口规范的第一道防线是中间件。认证、限流、熔断、日志这些横切关注点通过中间件统一处理,避免业务代码重复实现。Spring Boot框架用拦截器和过滤器实现类似功能,Go语言Gin框架则用更轻量的中间件链。本文用Go实战演示JWT认证中间件和令牌桶限流中间件的完整实现。

Gin中间件机制

Gin中间件本质上是一个返回HandlerFunc的函数。通过c.Next()形成调用链,请求按注册顺序进入,响应按逆序返回。

package main

import (
    "fmt"
    "time"
    "github.com/gin-gonic/gin"
)

// 日志中间件示例
func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        method := c.Request.Method

        // 请求前处理
        c.Next()

        // 请求后处理
        latency := time.Since(start)
        status := c.Writer.Status()
        fmt.Printf("[%s] %s %s %d %v\n",
            start.Format("2006-01-02 15:04:05"),
            method, path, status, latency)
    }
}

func main() {
    r := gin.Default()
    r.Use(LoggerMiddleware())

    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "pong"})
    })

    r.Run(":8080")
}

JWT认证中间件实现

服务治理中,JWT(JSON Web Token)是无状态认证的主流方案。相比Session方案,JWT不需要服务端存储会话信息,天然适合分布式部署。

package middleware

import (
    "net/http"
    "strings"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

// JWT签名密钥(生产环境从配置中心读取)
var jwtSecret = []byte("your-secret-key-change-in-production")

// Claims 自定义JWT声明
type Claims struct {
    UserID   int64  `json:"user_id"`
    Username string `json:"username"`
    Role     string `json:"role"`
    jwt.RegisteredClaims
}

// GenerateToken 生成JWT令牌
func GenerateToken(userID int64, username, role string) (string, error) {
    claims := Claims{
        UserID:   userID,
        Username: username,
        Role:     role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "yunthe-service",
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtSecret)
}

// ParseToken 解析JWT令牌
func ParseToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{},
        func(token *jwt.Token) (interface{}, error) {
            return jwtSecret, nil
        })
    if err != nil {
        return nil, err
    }
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    return nil, jwt.ErrTokenInvalid
}

// JWTAuth JWT认证中间件
func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 从Header获取Authorization
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "code":    401,
                "message": "缺少认证信息",
            })
            c.Abort()
            return
        }

        // 解析Bearer token
        parts := strings.SplitN(authHeader, " ", 2)
        if len(parts) != 2 || parts[0] != "Bearer" {
            c.JSON(http.StatusUnauthorized, gin.H{
                "code":    401,
                "message": "认证格式错误,应为Bearer Token",
            })
            c.Abort()
            return
        }

        // 解析token
        claims, err := ParseToken(parts[1])
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{
                "code":    401,
                "message": "无效或过期的令牌",
            })
            c.Abort()
            return
        }

        // 将用户信息存入上下文,后续handler可取用
        c.Set("user_id", claims.UserID)
        c.Set("username", claims.Username)
        c.Set("role", claims.Role)

        c.Next()
    }
}

// RequireRole 角色权限校验中间件
func RequireRole(roles ...string) gin.HandlerFunc {
    return func(c *gin.Context) {
        userRole, exists := c.Get("role")
        if !exists {
            c.JSON(http.StatusForbidden, gin.H{
                "code":    403,
                "message": "无权限访问",
            })
            c.Abort()
            return
        }

        roleStr := userRole.(string)
        allowed := false
        for _, r := range roles {
            if roleStr == r {
                allowed = true
                break
            }
        }

        if !allowed {
            c.JSON(http.StatusForbidden, gin.H{
                "code":    403,
                "message": "权限不足",
            })
            c.Abort()
            return
        }

        c.Next()
    }
}

令牌桶限流中间件

高并发设计中,限流是保护服务的关键手段。令牌桶算法以固定速率生成令牌,请求消耗令牌,桶满时丢弃多余令牌。相比计数器方案,令牌桶允许一定程度的突发流量。

package middleware

import (
    "net/http"
    "sync"
    "time"

    "github.com/gin-gonic/gin"
)

// TokenBucket 令牌桶
type TokenBucket struct {
    capacity    float64       // 桶容量
    rate        float64       // 令牌生成速率(个/秒)
    tokens      float64       // 当前令牌数
    lastRefill  time.Time     // 上次补充时间
    mu          sync.Mutex
}

// NewTokenBucket 创建令牌桶
func NewTokenBucket(capacity, rate float64) *TokenBucket {
    return &TokenBucket{
        capacity:   capacity,
        rate:       rate,
        tokens:     capacity, // 初始满桶
        lastRefill: time.Now(),
    }
}

// Allow 尝试获取一个令牌
func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()

    now := time.Now()
    // 计算自上次补充以来应该生成的令牌数
    elapsed := now.Sub(tb.lastRefill).Seconds()
    tb.tokens += elapsed * tb.rate

    // 令牌不超过桶容量
    if tb.tokens > tb.capacity {
        tb.tokens = tb.capacity
    }
    tb.lastRefill = now

    if tb.tokens >= 1 {
        tb.tokens -= 1
        return true
    }
    return false
}

// RateLimiter 基于IP的限流器
type RateLimiter struct {
    buckets map[string]*TokenBucket
    mu      sync.RWMutex
    capacity float64
    rate     float64
}

func NewRateLimiter(capacity, rate float64) *RateLimiter {
    return &RateLimiter{
        buckets:  make(map[string]*TokenBucket),
        capacity: capacity,
        rate:     rate,
    }
}

func (rl *RateLimiter) GetBucket(key string) *TokenBucket {
    rl.mu.RLock()
    bucket, exists := rl.buckets[key]
    rl.mu.RUnlock()

    if exists {
        return bucket
    }

    rl.mu.Lock()
    defer rl.mu.Unlock()

    // double-check
    if bucket, exists := rl.buckets[key]; exists {
        return bucket
    }

    bucket = NewTokenBucket(rl.capacity, rl.rate)
    rl.buckets[key] = bucket
    return bucket
}

// RateLimit 限流中间件
func RateLimit(capacity, rate float64) gin.HandlerFunc {
    limiter := NewRateLimiter(capacity, rate)

    return func(c *gin.Context) {
        key := c.ClientIP()
        bucket := limiter.GetBucket(key)

        if !bucket.Allow() {
            c.JSON(http.StatusTooManyRequests, gin.H{
                "code":    429,
                "message": "请求过于频繁,请稍后重试",
            })
            c.Abort()
            return
        }

        c.Next()
    }
}

熔断中间件

分布式事务中,下游服务故障时如果不及时熔断,级联失败会拖垮整个调用链。

package middleware

import (
    "net/http"
    "sync"
    "time"

    "github.com/gin-gonic/gin"
)

type CircuitState int

const (
    StateClosed   CircuitState = iota // 正常
    StateOpen                         // 熔断
    StateHalfOpen                     // 半开
)

type CircuitBreaker struct {
    mu              sync.Mutex
    state           CircuitState
    failureCount    int
    failureThreshold int      // 熔断阈值
    resetTimeout    time.Duration // 熔断恢复时间
    lastFailure     time.Time
    successCount    int
    halfOpenMax     int       // 半开状态最大请求数
}

func NewCircuitBreaker(threshold int, resetTimeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        state:            StateClosed,
        failureThreshold: threshold,
        resetTimeout:     resetTimeout,
        halfOpenMax:      3,
    }
}

func (cb *CircuitBreaker) Allow() bool {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    switch cb.state {
    case StateClosed:
        return true
    case StateOpen:
        if time.Since(cb.lastFailure) > cb.resetTimeout {
            cb.state = StateHalfOpen
            cb.successCount = 0
            return true
        }
        return false
    case StateHalfOpen:
        return cb.successCount < cb.halfOpenMax
    }
    return false
}

func (cb *CircuitBreaker) RecordSuccess() {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    if cb.state == StateHalfOpen {
        cb.successCount++
        if cb.successCount >= cb.halfOpenMax {
            cb.state = StateClosed
            cb.failureCount = 0
        }
    }
}

func (cb *CircuitBreaker) RecordFailure() {
    cb.mu.Lock()
    defer cb.mu.Unlock()

    cb.lastFailure = time.Now()
    cb.failureCount++

    if cb.state == StateHalfOpen {
        cb.state = StateOpen
        return
    }

    if cb.failureCount >= cb.failureThreshold {
        cb.state = StateOpen
    }
}

// CircuitBreakerMiddleware 熔断中间件
func CircuitBreakerMiddleware(cb *CircuitBreaker) gin.HandlerFunc {
    return func(c *gin.Context) {
        if !cb.Allow() {
            c.JSON(http.StatusServiceUnavailable, gin.H{
                "code":    503,
                "message": "服务暂时不可用,请稍后重试",
            })
            c.Abort()
            return
        }

        c.Next()

        if c.Writer.Status() >= 500 {
            cb.RecordFailure()
        } else {
            cb.RecordSuccess()
        }
    }
}

中间件组合使用

func main() {
    r := gin.Default()

    // 全局中间件
    r.Use(LoggerMiddleware())
    r.Use(RateLimit(100, 10)) // 桶容量100,每秒10个令牌

    cb := NewCircuitBreaker(5, 30*time.Second)

    // 公开接口
    public := r.Group("/api/v1")
    public.Use(CircuitBreakerMiddleware(cb))
    {
        public.POST("/login", loginHandler)
        public.POST("/register", registerHandler)
    }

    // 需要认证的接口
    auth := r.Group("/api/v1")
    auth.Use(CircuitBreakerMiddleware(cb))
    auth.Use(JWTAuth())
    {
        auth.GET("/profile", profileHandler)
        auth.PUT("/profile", updateProfileHandler)

        // 管理员接口
        admin := auth.Group("/admin")
        admin.Use(RequireRole("admin"))
        {
            admin.GET("/users", listUsersHandler)
            admin.DELETE("/users/:id", deleteUserHandler)
        }
    }

    r.Run(":8080")
}

消息中间件配合限流熔断,构成微服务的防护体系。中间件的执行顺序直接影响防护效果——限流应在认证之前(拦截无效请求),熔断应在业务逻辑之前(快速失败)。业务中台建设中,这套模式可作为标准基础设施复用。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/go-yu-yan-gin-kuang-jia-zhong-jian-jian-she-ji-jwt-ren/

(0)
小编小编
上一篇 11小时前
下一篇 11小时前

相关推荐