Golang没有全局请求拦截器,所有过滤必须实现在http.Handler链或框架中间件中;c.ClientIP()不可靠,因默认只信任回环网段,线上环境常返回127.0.0.1或内网地址,须通过X-Real-IP/X-Forwarded-For结合SetTrustedProxies解析真实IP,且拦截后必须c.Abort()并return,否则请求仍会继续执行。

直接说结论:Golang 没有“全局请求拦截器”这种抽象概念,所有过滤逻辑必须落在 http.Handler 链或框架中间件(如 Gin 的 gin.HandlerFunc)里实现;漏掉 c.Abort() 或忘记 return,就等于没拦截。
为什么 c.ClientIP() 不能直接用作拦截依据
线上环境几乎必然返回 127.0.0.1 或内网地址(比如 10.0.0.3),因为反向代理(Nginx/Traefik/CDN)会把真实 IP 放在 X-Forwarded-For 或 X-Real-IP 头里,而 c.ClientIP() 默认只信任本地回环网段。
- 必须显式配置可信代理:
engine.SetTrustedProxies([]string{"192.168.0.0/16", "203.208.60.0/24"}) - 若 Nginx 已设
proxy_set_header X-Real-IP $remote_addr,优先读c.Request.Header.Get("X-Real-IP") - 多层代理(CDN → Nginx → Go)时,取
X-Forwarded-For最左非信任地址,而非最右——攻击者可伪造尾部 IP - 不配置
SetTrustedProxies就解析X-Forwarded-For,等于把伪造头当真,黑白名单形同虚设
Gin 中间件里拦截失败后必须 abort + return
常见错误是写了 c.JSON(403, gin.H{"msg": "forbidden"}) 却没跟 c.Abort() 和 return,导致后续中间件甚至业务 handler 继续执行,可能已改数据库、发通知、写日志。
- 正确顺序:
c.JSON(403, ...); c.Abort(); return; -
c.Abort()只阻止后续中间件执行,不终止当前函数——漏掉return,c.Next()还会调用 - 不要在中间件里 defer 关闭资源(如 DB 连接),应绑定到
context.Context生命周期 - 静态路径(如
/public/*)也得过同一套拦截逻辑,否则 JS 文件可能泄露敏感接口路径
黑白名单结构必须支持 CIDR 且能热更新
用 map[string]bool 存 IP 列表看似简单,但无法匹配网段(如 192.168.1.0/24),且并发读写需锁,更致命的是规则更新时直接替换 map,会导致正在处理的请求看到新旧规则混杂状态。
立即学习“go语言免费学习笔记(深入)”;
- 推荐用第三方库如
github.com/spaolacci/murmur3+net.IPNet构建IPSet结构,支持 CIDR 查找 - 规则更新必须通过
atomic.Value写入新结构体指针,读取时Load().(*IPSet),零拷贝且线程安全 - 黑名单优先于白名单判断——先拒绝对方,再放行豁免路径(如
/healthz) - 从文件或 etcd 加载规则后,要校验格式(如
net.ParseCIDR("10.0.0.0/8")),非法条目应跳过而非 panic
真正难的不是写个 if 判断,而是确保每次请求拿到的都是完整、一致、实时生效的规则视图;热更新时哪怕差一个原子操作,就可能让恶意流量穿过缝隙。


















