Gin 怎么配置 SetTrustedProxies 与 TrustedPlatform 让 ClientIP 返回真实客户端 IP
2026/9/10 15:40:18 网站建设 项目流程

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()的注释和实现,它按以下顺序决定返回值:

  1. TrustedPlatform非空,且请求中存在该名称的头 → 直接返回该头的值;
  2. 否则从Request.RemoteAddr解析直接连接 IP(RemoteIP());
  3. 若直接连接 IP 落在SetTrustedProxies()设置的信任列表内,且ForwardedByClientIPtrue(gin.go 中New()的默认值就是true),则解析Engine.RemoteIPHeaders中定义的头(默认["X-Forwarded-For", "X-Real-IP"]);
  4. 头不合法、或直接连接 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

RunRunTLSRunUnixRunFdRunQUICRunListener这些启动入口都会检查信任列表是否覆盖全部 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),::/0192.168.0.0/16等组合合法;而192.168.1.33/33192.168.1.256gggg: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.PlatformGoogleAppEngineX-Appengine-Remote-Addr
gin.PlatformCloudflareCF-Connecting-IP
gin.PlatformFlyIOFly-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测试用例给出了一组可参照的行为数据(以下为测试用例中的示例值,不是生产环境的固定预期):

  • RemoteAddr40.40.40.40:42123X-Forwarded-For20.20.20.20, 30.30.30.30,信任列表只含40.40.40.40ClientIP()返回30.30.30.30(从后往前第一个不受信任的 IP);
  • 信任列表包含40.40.40.4030.30.30.3020.20.20.20(全部受信任)→ 返回头中第一个 IP20.20.20.20
  • 信任列表用 CIDR40.40.25.25/16+30.30.30.30时,直接连接40.40.40.40命中该 CIDR,同样返回20.20.20.20
  • 直接连接不受信任 → 返回直接连接 IP40.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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询