c.RealIP() 默认不生效,必须显式配置 e.IPExtractor 和 e.TrustProxyFunc 才可信;否则在反向代理后返回代理内网IP或伪造IP。

直接说结论:c.RealIP() 不是开箱即用的“真实 IP”,它必须配合 e.IPExtractor 和 e.TrustProxyFunc 才可信;否则在 Nginx / ALB / CDN 后,90% 的请求会返回代理内网地址或伪造 IP。
为什么 c.IP() 和 c.RealIP() 返回结果不一致
c.IP() 拿的是 TCP 连接对端地址(RemoteAddr),在直连场景下可用,但一上反向代理就变成 Nginx 或 ALB 的内网 IP(比如 10.0.1.5:32123);c.RealIP() 才是设计用来提取真实客户端 IP 的,但它默认行为是「不做任何解析」——只在你显式配置了提取器和信任规则后才生效。
- 没配
e.IPExtractor时,c.RealIP()等价于c.IP() - 没设
e.TrustProxyFunc时,Echo 会把所有带X-Forwarded-For的请求都视为不可信,直接丢弃该头,甚至可能 fallback 到100.64.0.0/10这类 CGNAT 地址(Cloudflare 常见) - 两者都缺 → 你拿到的永远是最后一跳代理的 IP,不是用户真实出口 IP
必须设置的两个关键配置项
这两个配置必须在 echo.New() 之后、任何路由注册之前完成,顺序错会导致中间件读不到正确值:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
-
e.IPExtractor = echo.ExtractIPFromXFFHeader():从X-Forwarded-For头取最右非私有 IP。注意它不校验可信性,只是“提取” -
e.TrustProxyFunc = func(c echo.Context) bool { return c.Request().Header.Get("X-Forwarded-For") != "" }:告诉 Echo “只要这个头存在,我就信这是一次被代理的请求”。更严谨的做法是结合可信代理段判断,例如:func(c echo.Context) bool { ip := net.ParseIP(c.Request().RemoteAddr) if ip == nil { return false } // 只信任来自公司 SLB 段和 Cloudflare 边缘段的请求 return isCloudflareIP(ip) || ip.InRange(net.ParseIP("192.168.10.0"), net.ParseIP("192.168.10.255")) }
白名单中间件里怎么安全比对 CIDR
别用字符串 strings.Contains 或正则匹配 IP 段——192.168.1.100 会被 "192.168.1" 错误匹配。必须用标准库 net.IPNet.Contains():
- 先将白名单字符串(如
"192.168.0.0/16")转成*net.IPNet:_, ipnet, _ := net.ParseCIDR("192.168.0.0/16") - 再拿客户端 IP(
c.RealIP()返回的string)转net.IP,调ipnet.Contains(ip) - 注意 IPv4/v6 兼容:如果白名单含
::1/128,客户端传::ffff:127.0.0.1就不会命中,得提前做规范化(如用ip.To4()强制转 IPv4)
容易被忽略的三个坑
线上出问题最多的地方,往往就在这三处:
- Nginx 配置漏转发头:必须确保有
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;,且不能写成$remote_addr—— 否则X-Forwarded-For里只有代理自身 IP,没有原始链路 - Cloudflare 用户没加信任段:Cloudflare 回源 IP 是动态的,需定期同步其官方 IP 段(
https://www.cloudflare.com/ips-v*),并用net.ParseCIDR加入信任列表,否则c.RealIP()会退化为100.64.0.0/10 - 中间件顺序放错:IP 提取逻辑必须在黑白名单中间件之前执行;如果把黑白名单中间件注册在
e.Use()之前,而 IP 配置在之后,那中间件看到的c.RealIP()还是未初始化状态

















