req.RemoteAddr 不可信,因它仅返回最后一跳代理IP;应先校验RemoteAddr是否属可信代理网段,再解析X-Forwarded-For中首个非私有IP,否则用X-Real-IP或直连IP。

直接用 req.RemoteAddr 几乎总是错的——它只返回最后一跳代理的 IP,不是用户真实出口 IP。
为什么 req.RemoteAddr 不可信
客户端请求通常经过 Nginx、CDN 或云负载均衡(如阿里云 SLB、AWS ALB),req.RemoteAddr 拿到的是这些中间件的内网 IP(比如 10.0.1.5:42183)。如果拿它做限流、日志或风控,会把多个用户聚成一个 IP,或者把单个用户误判为不同来源。
- 常见错误现象:
req.RemoteAddr在本地开发时看似正常(直连时确实是客户端 IP),一上生产就全变成内网地址 - X-Forwarded-For 可被伪造,所以不能无条件信任;必须先确认请求来源是否在你可控的可信代理列表里
- Nginx 需显式配置
proxy_set_header X-Real-IP $remote_addr;才能让X-Real-IP可靠,否则该 header 可能为空或被污染
安全提取真实 IP 的核心逻辑
关键不是“取哪个 header”,而是“谁发来的请求”。标准做法是:先检查 req.RemoteAddr 是否属于你信任的代理网段(比如 Nginx 所在的 10.0.0.0/8 或 172.16.0.0/12),只有它是可信代理,才去解析 X-Forwarded-For 或 X-Real-IP。
- 用
net.ParseIP(strings.Split(req.RemoteAddr, ":")[0])提取直连 IP - 写一个
isTrustedProxy(ip net.IP)函数,白名单校验(别硬编码字符串比较,用ip.In(mynet)) - 若可信,则遍历
X-Forwarded-For逗号分隔的 IP 列表,取最左第一个非私有 IP(用net.ParseIP(ip).IsPrivate()判断) - 若不可信,直接返回该直连 IP——此时它就是真实客户端
- 别自己写字符串切分 + trim + 正则校验,Go 标准库的
net和strings足够稳
X-Forwarded-For 解析时容易踩的坑
X-Forwarded-For 格式是 "203.0.113.195, 203.0.113.196, 203.0.113.197",最左是原始客户端,但攻击者可手动加头伪造。不校验代理链就直接取第一个,等于开门揖盗。
- 常见错误:直接
strings.Split(header, ",")[0]→ 返回"1.2.3.4"(攻击者伪造) - 正确做法:对每个分段调用
strings.TrimSpace后,再用net.ParseIP解析并.IsPrivate()过滤 - 注意空格:
"192.168.1.1 , 10.0.0.2"中的空格必须 trim,否则net.ParseIP返回 nil - 如果所有分段都是私有 IP(比如全是
10.x.x.x),应 fallback 到X-Real-IP,再 fallback 到直连 IP
要不要用第三方库?
像 github.com/microcosm-cc/bluemonday 或 github.com/gorilla/handlers 里的 IP 提取函数,多数只是封装了类似逻辑,并未解决「信任代理判定」这个核心问题。自己写 20 行以内就能覆盖全部场景,且可控性更强。
真正复杂的是部署侧:Nginx 的 proxy_set_header 配置、K8s Service 的 externalTrafficPolicy、云厂商 LB 的源 IP 透传开关——这些不在 Go 代码里,但决定了你的 Go 代码有没有得用。


















