Gin 怎么配置 SetTrustedProxies 与 TrustedPlatform 让 ClientIP 返回真实客户端 IP
【免费下载链接】ginGin is a high-performance HTTP web framework written in Go. It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to httprouter. Gin is designed for building REST APIs, web applications, and microservices.项目地址: https://gitcode.com/GitHub_Trending/gi/gin
Gin 服务放在反向代理或 CDN 后面时,c.ClientIP()应该返回真实客户端 IP,而不是代理的地址。但 Gin 默认信任所有代理(TrustedProxies功能默认开启且默认信任全部来源),这意味着任何人都可以伪造X-Forwarded-For头。这篇文档给出两条互补的配置路径:用Engine.SetTrustedProxies()限定哪些直接连接来源的 IP 头可以被信任(自建代理场景),以及在接入 CDN 时用Engine.TrustedPlatform直接读取 CDN 写入的专用头。前提是一个已引入 Gin 的 Go 项目。
ClientIP() 的判定链
先看 context.go 中ClientIP()的注释和实现,它按以下顺序决定返回值:
TrustedPlatform非空,且请求中存在该名称的头 → 直接返回该头的值;- 否则从
Request.RemoteAddr解析直接连接 IP(RemoteIP()); - 若直接连接 IP 落在
SetTrustedProxies()设置的信任列表内,且ForwardedByClientIP为true(gin.go 中New()的默认值就是true),则解析Engine.RemoteIPHeaders中定义的头(默认["X-Forwarded-For", "X-Real-IP"]); - 头不合法、或直接连接 IP 不受信任 → 返回直接连接 IP。
其中第 3 步的具体规则在 gin.go 的validateHeader中实现:X-Forwarded-For按逗号拆分后从后往前检查,返回第一个不在信任列表中的 IP;如果所有 IP 都在信任列表里,返回头中的第一个 IP;遇到无法解析的条目则停止并判定该头无效。
起步检查:默认配置会打印警告
Gin 的默认信任列表是0.0.0.0/0与::/0(gin.go 中New()的初始值),即信任全部代理。docs/doc.md 明确标注:this is NOT safe。
Run、RunTLS、RunUnix、RunFd、RunQUIC、RunListener这些启动入口都会检查信任列表是否覆盖全部 IP,若是则打印:
[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.因此"启动时是否出现这条警告"可以作为一条现成的核对信号:配置正确后它不应再出现。
步骤一:用 SetTrustedProxies 限定可信代理
按 docs/doc.md 的 "Don't trust all proxies" 一节,在gin.Engine上调用SetTrustedProxies(),参数支持 IPv4 地址、IPv4 CIDR、IPv6 地址、IPv6 CIDR。文档给出的示例(192.168.1.2替换为你自己的代理地址):
func main() { router := gin.Default() router.SetTrustedProxies([]string{"192.168.1.2"}) router.GET("/", func(c *gin.Context) { // If the client is 192.168.1.2, use the X-Forwarded-For // header to deduce the original client IP from the trust- // worthy parts of that header. // Otherwise, simply return the direct client IP fmt.Printf("ClientIP: %s\n", c.ClientIP()) }) router.Run() }router.Run()未指定端口时默认监听:8080(除非设置了PORT环境变量),文档路由章节有说明。
几个直接影响执行的细节:
- 返回值是
error,必须检查。gin_test.go 的TestPrepareTrustedCIRDsWith展示了哪些输入合法:192.168.1.33合法(单个 IP 会被解析为/32),2002:0000:0000:1234:abcd:ffff:c0a8:0101合法(IPv6 单地址解析为/128),::/0、192.168.0.0/16等组合合法;而192.168.1.33/33、192.168.1.256、gggg:0000:0000:1234:abcd:ffff:c0a8:0101这类无效值会返回 error。 - 完全不使用代理时,用
router.SetTrustedProxies(nil)禁用该功能,此时Context.ClientIP()直接返回直接连接地址,避免不必要的计算——这是 docs/doc.md 给出的做法,文档原文说明其目的是 avoid some unnecessary computation。 - 列表为空切片
[]string{}与nil效果不同:nil是"关闭功能",空切片是"没有任何可信代理"。两者的结果都是ClientIP()回退到直接连接 IP(见 context_test.go 中两条相邻的测试断言),但文档明确描述的是nil这一种,优先使用文档说明的写法。
步骤二:接入 CDN 时改用 TrustedPlatform
docs/doc.md 的 Notice 指出:Engine.TrustedPlatform可以跳过 TrustedProxies 检查,它的优先级高于 TrustedProxies。设置后,ClientIP()会解析对应的头并直接返回其中的 IP。
gin.go 中预定义了三个平台常量,对应各自读取的头:
| 常量 | 读取的请求头 |
|---|---|
gin.PlatformGoogleAppEngine | X-Appengine-Remote-Addr |
gin.PlatformCloudflare | CF-Connecting-IP |
gin.PlatformFlyIO | Fly-Client-IP |
文档示例(192.168.1.2一节的同款结构,此处为 CDN 分支):
func main() { router := gin.Default() // Use predefined header gin.PlatformXXX // Google App Engine router.TrustedPlatform = gin.PlatformGoogleAppEngine // Cloudflare router.TrustedPlatform = gin.PlatformCloudflare // Fly.io router.TrustedPlatform = gin.PlatformFlyIO // Or, you can set your own trusted request header. But be sure your CDN // prevents users from passing this header! For example, if your CDN puts // the client IP in X-CDN-Client-IP: router.TrustedPlatform = "X-CDN-Client-IP" router.GET("/", func(c *gin.Context) { // If you set TrustedPlatform, ClientIP() will resolve the // corresponding header and return IP directly fmt.Printf("ClientIP: %s\n", c.ClientIP()) }) router.Run() }注意文档的警告:如果用自己的头名(如X-CDN-Client-IP),必须确认你的 CDN 会阻止用户直接传入该头,否则客户端仍可伪造。另外,当请求中该头为空时,ClientIP()不会报错,而是回落到 TrustedProxies 判定路径(context_test.go 中有对应断言:TrustedPlatform指向一个不存在的头时,返回直接连接 IP)。
验证配置是否生效
1. 启动检查:运行服务,确认[WARNING] You trusted all proxies, this is NOT safe.不再出现在启动日志中。
2. 请求检查:按步骤一/二的示例程序运行,用curl http://localhost:8080/发请求,观察 handler 打印的ClientIP:输出是否符合预期。context_test.go 的ClientIP测试用例给出了一组可参照的行为数据(以下为测试用例中的示例值,不是生产环境的固定预期):
RemoteAddr为40.40.40.40:42123,X-Forwarded-For为20.20.20.20, 30.30.30.30,信任列表只含40.40.40.40→ClientIP()返回30.30.30.30(从后往前第一个不受信任的 IP);- 信任列表包含
40.40.40.40、30.30.30.30、20.20.20.20(全部受信任)→ 返回头中第一个 IP20.20.20.20; - 信任列表用 CIDR
40.40.25.25/16+30.30.30.30时,直接连接40.40.40.40命中该 CIDR,同样返回20.20.20.20; - 直接连接不受信任 → 返回直接连接 IP
40.40.40.40; X-Forwarded-For内容不是合法 IP(如" blah ")→ 回退返回直接连接 IP。
3. 错误检查:SetTrustedProxies返回非 nil 的error说明列表里有无法解析的地址或 CIDR,需要修正输入(格式见上一步的合法/非法示例)。
限制与已废弃项
- 默认"信任所有代理"是不安全状态,docs/doc.md 原文标注this is NOT safe;不配置
SetTrustedProxies或接入 CDN 前的状态都应视为待修正项。 Engine.AppEngine字段已废弃,gin.go 中的注释要求改用TrustedPlatform并取值gin.PlatformGoogleAppEngine。旧标志仍然生效(读取X-Appengine-Remote-Addr头),但每次请求都会打印一条 deprecation 日志(context.go 中ClientIP()的实现)。- 需要更换读头顺序时,可调整
Engine.RemoteIPHeaders,默认为["X-Forwarded-For", "X-Real-IP"];ClientIP()按该列表顺序逐个尝试,取第一个能解析出有效 IP 的头。
配置完成后,核对点就是两个:启动日志无 trust-all 警告,handler 打印的ClientIP:与代理链中的真实来源一致。更多细节可回到 docs/doc.md 的 "Don't trust all proxies" 一节。
【免费下载链接】ginGin is a high-performance HTTP web framework written in Go. It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to httprouter. Gin is designed for building REST APIs, web applications, and microservices.项目地址: https://gitcode.com/GitHub_Trending/gi/gin
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考