gin-contrib/limiter不能直接防CC攻击,因其仅做请求频率统计、无法获取真实IP、key单一且无自动封禁能力;需结合可信代理头解析、复合key设计、内存封禁存储及差异化响应策略构建完整防护。

为什么 gin-contrib/limiter 不能直接用于防 CC 攻击
它只做请求频率统计,不记录来源行为特征,更不会自动封禁 IP。CC 攻击的核心是「短时间高频、低价值请求」,比如反复刷 /login 或 /api/v1/order/{id},而默认限流中间件连真实 IP 都拿不准,更别说识别恶意模式。
典型问题包括:c.ClientIP() 返回 127.0.0.1 或内网地址;限流 key 固定为 ip,无法区分同一 IP 下正常用户和爬虫共存的场景;没有封禁状态持久化,重启即失效。
如何提取真实客户端 IP 并构造可封禁的 key
必须绕过 c.ClientIP(),改用可信代理链解析:X-Forwarded-For(多层代理)或 X-Real-IP(单层)。关键三步:
- 在 Nginx/Traefik 中显式设置
proxy_set_header X-Real-IP $remote_addr,并确保只对可信代理网段生效 - Gin 启动时调用
engine.SetTrustedProxies([]string{"192.168.0.0/16", "10.0.0.0/8"}),否则c.ClientIP()仍不可信 - 封禁 key 不应只是 IP,建议组合为
ip:ua_hash:path_prefix,例如"203.208.60.42:abc123:/api/v1/login",避免误伤 CDN 或 NAT 用户
封禁状态怎么存、怎么查才不拖慢请求
封禁判断必须在毫秒内完成,不能查 DB、不能加锁、不能每次解析 CIDR。推荐方案:
- 用
map[string]time.Time存活跃封禁项,key 是上面构造的复合 key,value 是解封时间戳 - 用
sync.RWMutex保护写操作(仅封禁/解封时写),读操作无锁(time.Now().Before(expire)判断) - 定期 goroutine 清理过期项(比如每 30 秒扫描一次),不阻塞主请求流
- 绝对不要用
redis.GET实时查——单次 RTT 就可能超 5ms,压测时直接雪崩
封禁触发后怎么响应才不暴露防御逻辑
返回 429 是明确告诉攻击者“你被限了”,反而帮其调参。生产环境应混用策略:
- 对高危路径(如
/login、/register)直接返回403+ 空 body,不带任何 header - 对普通接口,可返回
200+ 假数据(如空 JSON 或静态 HTML),但记录日志标记为blocked_by_cc - 所有封禁动作必须写入审计日志,字段至少含:
ip、user_agent、first_blocked_at、reason(如 “5 次 /login in 10s”)
最易忽略的是:封禁规则变更(如新增 CIDR 范围)必须热更新,不能 reload 进程;否则运维半夜改个配置,服务就中断了。


















