c.clientip() 返回内网地址是因为默认只信任127.0.0.1/8和::1/128,nginx/slb/cdn不在可信列表中,导致回退到remoteaddr;需同步配置nginx转发头与echo的ipextractor及settrustedproxies,并校验私有ip。

为什么 c.ClientIP() 在 Echo 里也返回内网地址
因为 c.ClientIP() 默认只信任 127.0.0.1/8 和 ::1/128 这些本地段,而你的 Nginx、SLB 或 CDN 都不在这个列表里。结果它直接跳过 X-Forwarded-For,fallback 到 c.Request.RemoteAddr(比如 10.0.1.5:42193)。这不是 Echo 的 bug,是默认安全策略——你得主动告诉它“谁可信”。
必须同时配置 Nginx 和 Echo 的可信代理段
Nginx 少了这三行,Echo 再怎么配都没用:
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
Echo 启动时(e := echo.New() 之后、e.Start() 之前)必须显式设置:
-
e.IPExtractor = echo.ExtractIPFromXFFHeader(启用从X-Forwarded-For解析) -
e.SetTrustedProxies([]string{"10.0.0.0/8", "192.168.0.0/16", "127.0.0.1/32"})(填你实际的代理网段,比如阿里云 SLB 回源段、K8s Service CIDR)
注意:SetTrustedProxies 必须传 CIDR 格式,不能只写 "10.0.1.5";如果用了 CDN,它的出口 IP 也要加进这个列表,否则会被当作客户端 IP。
手动解析 X-Forwarded-For 更可控,但必须校验合法性
绕过 c.ClientIP() 直接读头,适合需要精细控制或部署环境不可控的场景:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 先取
c.Request.Header.Get("X-Forwarded-For"),按", "分割 - 倒序遍历(从右往左),跳过所有在
trustedProxies列表里的 IP - 对每个候选 IP 调用
net.ParseIP()+ 排除私有地址(127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、::1) - 第一个通过校验的才是真实客户端 IP
别用 strings.Split(...)[0] 取第一个——攻击者发 X-Forwarded-For: 1.1.1.1, 127.0.0.1 就能绕过风控。
中间件里获取 IP 的典型写法
推荐封装成可复用函数,避免每次手写校验逻辑:
func getRealIP(c echo.Context) string {
ip := c.Request().Header.Get("X-Real-IP")
if ip != "" && net.ParseIP(ip) != nil && !isPrivateIP(ip) {
return ip
}
ips := strings.Split(c.Request().Header.Get("X-Forwarded-For"), ", ")
for i := len(ips) - 1; i >= 0; i-- {
ip := strings.TrimSpace(ips[i])
if ip != "" && net.ParseIP(ip) != nil && !isPrivateIP(ip) {
return ip
}
}
return c.Request().RemoteAddr[:strings.LastIndex(c.Request().RemoteAddr, ":")]
}
关键点:优先用 X-Real-IP(单值、不易伪造),再 fallback 到 X-Forwarded-For 倒序取;RemoteAddr 只作最后兜底,且要切掉端口。
最易被忽略的是私有 IP 校验和可信代理段同步——Nginx 加了头,Echo 没设信任段,或者信任段漏了某台 LB 的出口 IP,都会导致取到错误 IP。










