gin中注册ip黑名单中间件需返回func(*gin.context)函数,路由前调用engine.use(ipblacklistmiddleware(blacklist)),确保c.next()调用且检查真实ip(如c.clientip()配合可信代理配置),黑名单用map[string]struct{}或sync.map。

如何在 Gin 中注册一个生效的 IP 黑名单中间件
直接写 gin.Engine.Use() 不够,中间件必须返回 func(*gin.Context) 类型函数,且需在路由注册前调用。常见错误是把逻辑写在中间件外、或漏掉 c.Next() 导致后续 handler 不执行。
- 中间件函数必须接收
*gin.Context参数,并在检查通过后调用c.Next() - 注册顺序很重要:黑名单中间件要放在所有业务路由之前,否则无效
- 推荐用
engine.Use(ipBlacklistMiddleware(blacklist))方式注册,blacklist是map[string]struct{}或sync.Map(并发安全)
获取真实客户端 IP 而不是 127.0.0.1 或代理地址
Gin 默认的 c.ClientIP() 会尝试从 X-Forwarded-For、X-Real-IP 等头中提取,但若反向代理(如 Nginx)没配置透传,就会退化为 RemoteAddr —— 这在 Docker 或负载均衡后基本等于上游服务器 IP。
- 确保反向代理设置了
proxy_set_header X-Real-IP $remote_addr;和proxy_set_header X-Forwarded-For $remote_addr; - Gin 启动时可显式启用信任代理:
engine.ForwardedBy = "X-Forwarded-For",并设置可信 CIDR(如engine.SetTrustedProxies([]string{"10.0.0.0/8"})) - 若无法控制代理层,临时方案是读
c.GetHeader("X-Real-IP")并手动校验格式,但不推荐用于生产
黑名单数据结构选 map[string]struct{} 还是 sync.Map
如果黑名单只在启动时加载、运行中不更新,用普通 map[string]struct{} + sync.RWMutex 保护读写即可;若支持热更新(如从 Redis 或文件监听变更),必须用 sync.Map 或带锁封装。
-
map[string]struct{}查找 O(1),内存占用小,适合静态列表( -
sync.Map无锁读性能好,但写操作开销略高,且不支持遍历 —— 如果需要定期清理过期 IP,它反而更麻烦 - 避免用
[]string存黑名单:每次请求都要遍历,1000 条就明显拖慢 QPS
返回 403 时如何避免影响日志和监控指标
直接 c.AbortWithStatus(403) 会让 Gin 默认日志记录该请求,且 Prometheus 的 http_request_duration_seconds 也会统计进去 —— 这会导致“被拦截请求”污染成功率指标。
- 建议改用
c.Abort()+ 手动写响应:c.Writer.WriteHeader(403)+c.Writer.Write([]byte("Forbidden")) - 这样 Gin 的
Logger中间件不会记录该请求(因未进入后续链路),也不会触发Recovery捕获 - 若需记录拦截行为,单独打一条结构化日志(如用
logrus.WithField("ip", ip).Warn("blocked by ip blacklist")),别混进访问日志
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











