全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-18 8 分钟 ✍️ juanwangdev

Gin 速率限制与防暴力破解

速率限制是保护 API 免受滥用和暴力破解攻击的关键措施。

速率限制算法

令牌桶算法

Go
import (
    "sync"
    "time"
)

type TokenBucket struct {
    capacity  int64       // 桶容量
    tokens    int64       // 当前令牌数
    rate      int64       // 令牌产生速率(个/秒)
    lastTime  time.Time   // 上次更新时间
    mu        sync.Mutex
}

func NewTokenBucket(capacity, rate int64) *TokenBucket {
    return &TokenBucket{
        capacity: capacity,
        tokens:   capacity,
        rate:     rate,
        lastTime: time.Now(),
    }
}

func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()

    now := time.Now()
    elapsed := now.Sub(tb.lastTime).Seconds()
    tb.lastTime = now

    // 补充令牌
    tb.tokens += int64(elapsed * float64(tb.rate))
    if tb.tokens > tb.capacity {
        tb.tokens = tb.capacity
    }

    if tb.tokens > 0 {
        tb.tokens--
        return true
    }
    return false
}

滑动窗口算法

Go
type SlidingWindow struct {
    capacity int64
    window   time.Duration
    requests []time.Time
    mu       sync.Mutex
}

func NewSlidingWindow(capacity int64, window time.Duration) *SlidingWindow {
    return &SlidingWindow{
        capacity: capacity,
        window:   window,
        requests: make([]time.Time, 0),
    }
}

func (sw *SlidingWindow) Allow() bool {
    sw.mu.Lock()
    defer sw.mu.Unlock()

    now := time.Now()
    cutoff := now.Add(-sw.window)

    // 移除过期请求
    validRequests := make([]time.Time, 0)
    for _, t := range sw.requests {
        if t.After(cutoff) {
            validRequests = append(validRequests, t)
        }
    }
    sw.requests = validRequests

    // 检查是否超过限制
    if int64(len(sw.requests)) < sw.capacity {
        sw.requests = append(sw.requests, now)
        return true
    }
    return false
}

使用 gin-limiter 中间件

Go
import (
    "github.com/gin-contrib/limiter"
    "github.com/gin-contrib/limiter/redis"
    "github.com/gin-gonic/gin"
    "time"
)

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

    // 基于内存的速率限制
    store := limiter.NewMemoryStore()
    rateLimiter := limiter.New(store, limiter.Options{
        Max:      100,             // 最大请求数
        Duration: time.Minute,     // 时间窗口
        KeyFunc:  limiter.DefaultKeyFunc, // 默认使用 IP
    })

    r.Use(rateLimiter)
}

自定义速率限制中间件

基于 IP 的限流

Go
import (
    "sync"
    "time"
)

type IPRateLimiter struct {
    ips map[string]*TokenBucket
    mu  sync.RWMutex
    capacity int64
    rate     int64
}

func NewIPRateLimiter(capacity, rate int64) *IPRateLimiter {
    return &IPRateLimiter{
        ips:      make(map[string]*TokenBucket),
        capacity: capacity,
        rate:     rate,
    }
}

func (l *IPRateLimiter) GetLimiter(ip string) *TokenBucket {
    l.mu.Lock()
    defer l.mu.Unlock()

    limiter, exists := l.ips[ip]
    if !exists {
        limiter = NewTokenBucket(l.capacity, l.rate)
        l.ips[ip] = limiter
    }
    return limiter
}

func RateLimitMiddleware(limiter *IPRateLimiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        ip := c.ClientIP()
        if !limiter.GetLimiter(ip).Allow() {
            c.JSON(429, gin.H{
                "error": "请求过于频繁,请稍后再试",
            })
            c.Abort()
            return
        }
        c.Next()
    }
}

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

    // 每 IP 每秒最多 10 个请求
    limiter := NewIPRateLimiter(10, 10)
    r.Use(RateLimitMiddleware(limiter))

    r.Run(":8080")
}

基于用户 ID 的限流

Go
type UserRateLimiter struct {
    users    map[string]*TokenBucket
    mu       sync.RWMutex
    capacity int64
    rate     int64
}

func RateLimitByUser(limiter *UserRateLimiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        userID, exists := c.Get("user_id")
        if !exists {
            c.Next()
            return
        }

        key := userID.(string)
        if !limiter.GetLimiter(key).Allow() {
            c.JSON(429, gin.H{"error": "操作过于频繁"})
            c.Abort()
            return
        }
        c.Next()
    }
}

防暴力破解策略

登录限流

Go
type LoginAttempt struct {
    attempts map[string]int
    blocked  map[string]time.Time
    mu       sync.RWMutex
}

func NewLoginAttempt() *LoginAttempt {
    return &LoginAttempt{
        attempts: make(map[string]int),
        blocked:  make(map[string]time.Time),
    }
}

func (la *LoginAttempt) CheckBlocked(username string) bool {
    la.mu.RLock()
    defer la.mu.RUnlock()

    blockedTime, exists := la.blocked[username]
    if !exists {
        return false
    }

    // 封禁 30 分钟
    if time.Since(blockedTime) > 30*time.Minute {
        return false
    }
    return true
}

func (la *LoginAttempt) RecordAttempt(username string) bool {
    la.mu.Lock()
    defer la.mu.Unlock()

    la.attempts[username]++

    // 5 次失败后封禁
    if la.attempts[username] >= 5 {
        la.blocked[username] = time.Now()
        delete(la.attempts, username)
        return true
    }
    return false
}

func (la *LoginAttempt) ResetAttempt(username string) {
    la.mu.Lock()
    defer la.mu.Unlock()
    delete(la.attempts, username)
}

func LoginMiddleware(la *LoginAttempt) gin.HandlerFunc {
    return func(c *gin.Context) {
        username := c.PostForm("username")

        if la.CheckBlocked(username) {
            c.JSON(403, gin.H{"error": "账户已被锁定,请 30 分钟后再试"})
            c.Abort()
            return
        }
        c.Next()
    }
}

使用 Redis 分布式限流

Go
import (
    "github.com/go-redis/redis/v8"
    "context"
    "time"
)

type RedisRateLimiter struct {
    client   *redis.Client
    capacity int64
    window   time.Duration
}

func NewRedisRateLimiter(client *redis.Client, capacity int64, window time.Duration) *RedisRateLimiter {
    return &RedisRateLimiter{
        client:   client,
        capacity: capacity,
        window:   window,
    }
}

func (r *RedisRateLimiter) Allow(ctx context.Context, key string) bool {
    now := time.Now().UnixNano()
    windowStart := now - int64(r.window)

    pipe := r.client.Pipeline()
    // 移除过期记录
    pipe.ZRemRangeByScore(ctx, key, "0", string(windowStart))
    // 添加当前请求
    pipe.ZAdd(ctx, key, &redis.Z{Score: float64(now), Member: now})
    // 统计当前窗口请求数
    pipe.ZCard(ctx, key)
    // 设置过期时间
    pipe.Expire(ctx, key, r.window)

    cmds, _ := pipe.Exec(ctx)
    count := cmds[2].(*redis.IntCmd).Val()

    return count <= r.capacity
}

func RedisRateLimitMiddleware(limiter *RedisRateLimiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        key := "rate_limit:" + c.ClientIP()

        if !limiter.Allow(context.Background(), key) {
            c.JSON(429, gin.H{"error": "请求过于频繁"})
            c.Abort()
            return
        }
        c.Next()
    }
}

限流策略对比

策略适用场景优点缺点
内存限流单机应用简单高效不支持分布式
Redis 限流分布式应用数据共享需要 Redis
令牌桶允许突发流量平滑限流实现稍复杂
滑动窗口精确限流控制精确内存占用高

注意:生产环境建议使用 Redis 实现分布式限流,保证多实例一致性。

要点总结

  1. 令牌桶算法:允许一定突发流量,适合 API 限流
  2. 滑动窗口:精确控制请求频率,适合严格限流
  3. 登录保护:失败次数限制 + 账户锁定机制
  4. 分布式限流:使用 Redis 保证多实例一致性
  5. 合理配置:根据业务场景设置限流阈值

📝 发现内容有误?点击此处直接编辑

← 上一篇 Gin 中间件安全控制
下一篇 → Gin Mock测试
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库