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 实现分布式限流,保证多实例一致性。
要点总结
- 令牌桶算法:允许一定突发流量,适合 API 限流
- 滑动窗口:精确控制请求频率,适合严格限流
- 登录保护:失败次数限制 + 账户锁定机制
- 分布式限流:使用 Redis 保证多实例一致性
- 合理配置:根据业务场景设置限流阈值
📝 发现内容有误?点击此处直接编辑