golang获取客户端真实 IP

获取客户端 IP 看似简单,实际涉及网络架构与安全信任两个维度的权衡。服务是否经过代理、代理是否可信,直接决定了获取策略。

1. IP 获取优先级

以下是获取客户端真实 IP 的标准优先级,适用于部署在可信代理(Nginx、AWS ELB、Cloudflare 等)后的服务:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#4F46E5', 'primaryTextColor': '#000', 'primaryBorderColor': '#3730A3', 'lineColor': '#6366F1', 'secondaryColor': '#10B981', 'tertiaryColor': '#F59E0B'}}}%%
flowchart TD
    A(["请求到达"]) --> B{"X-Forwarded-For 存在?"}
    B -->|"是"| C["取第一个 IP"]
    B -->|"否"| D{"X-Real-IP 存在?"}
    D -->|"是"| E["使用 X-Real-IP"]
    D -->|"否"| F["使用 RemoteAddr"]
    C --> G(["返回客户端 IP"])
    E --> G
    F --> G

    classDef primary fill:#4F46E5,stroke:#3730A3,color:#fff
    classDef success fill:#10B981,stroke:#059669,color:#fff
    classDef decision fill:#F59E0B,stroke:#D97706,color:#000

    class A,G primary
    class C,E,F success
    class B,D decision

1.1 实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"fmt"
"net"
"net/http"
"strings"
)

// GetClientIP 按优先级解析客户端 IP
func GetClientIP(r *http.Request) string {
// 优先级 1: X-Forwarded-For(代理链路)
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
if ips := strings.Split(xff, ","); len(ips) > 0 {
if clientIP := strings.TrimSpace(ips[0]); clientIP != "" {
return clientIP
}
}
}

// 优先级 2: X-Real-IP(Nginx 常用)
if xri := r.Header.Get("X-Real-IP"); xri != "" {
return xri
}

// 优先级 3: RemoteAddr(直连 IP)
if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return ip
}
return r.RemoteAddr
}

func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "客户端 IP: %s", GetClientIP(r))
}

func main() {
http.HandleFunc("/", handler)
fmt.Println("Server started at :8080")
http.ListenAndServe(":8080", nil)
}

2. 三种 IP 来源详解

2.1 X-Forwarded-For

标准 HTTP 头,记录请求链路中所有节点的 IP。

属性说明
格式ClientIP, Proxy1, Proxy2
特点每级代理追加自身 IP 到列表末尾
取值通常取第一个作为原始客户端 IP
风险可被伪造,需结合可信代理使用

2.2 X-Real-IP

约定俗成的 HTTP 头,由 Nginx 等反向代理设置。

Nginx 典型配置:

1
proxy_set_header X-Real-IP $remote_addr;

如果服务直接暴露公网,用户可伪造此头。只有经过可信代理(会覆盖伪造值)时,该值才可信。

2.3 RemoteAddr

TCP 层信息,代表直接连接方的 IP。

场景RemoteAddr 值
用户直连用户真实 IP,不可伪造
经过代理代理服务器 IP(如 127.0.0.1

3. 安全最佳实践

盲目信任 X-Forwarded-ForX-Real-IP 存在安全风险,只有当服务运行在可信代理后时,才应信任这些 Header。

trustedProxies 的核心含义就是:” 哪些上游节点(代理/负载均衡)是我自己控制的、可信的 “。
只有当请求确实是从这些可信节点转发过来的时候,你才去相信 X-Forwarded-For / X-Real-IP / Forwarded 这些头;否则这些头很容易被客户端自己伪造。

3.1 Nginx

在你把 nginx 放在业务服务前面并用 proxy_pass 转发时,nginx 就是反向代理(reverse proxy)。

流程大概是: 客户端 → nginx → 你的 Go 服务。此时 Go 服务看到的 TCP 连接是 “nginx → Go”,所以 r.RemoteAddr 代表的是 nginx 的 IP(而不是客户端)。

  • 用了 nginx,remoteIP 一定是 127.0.0.1 吗
    • nginx 和 Go 在同一台机器,nginx 转发到 127.0.0.1:xxxx,这时 Go 收到的连接来自本机,所以很可能:r.RemoteAddr 是 127.0.0.1: 端口(IPv4)
    • nginx 和 Go 不在同一台机器,r.RemoteAddr 会是 nginx 的内网 IP(例如 10.x、172.16.x、192.168.x),而不是 127.0.0.1
  • nginx 怎么配,才能把真实 IP 传给 Go?
1
2
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

3.2 为什么要 trustedProxies

因为如果你在 Go 里 “ 无条件信任 X-Forwarded-For”,攻击者可以直接发请求加个头:X-Forwarded-For: 1.2.3.4。你就会被骗,以为他来自 1.2.3.4。

  • 如果请求是从 nginx(可信代理)来的 → 才读取 X-Forwarded-For / X-Real-IP
  • 如果请求不是从可信代理来的 → 只用 RemoteAddr

trustedProxies 在 Go 里应该填什么?

一般填 “ 可能作为最后一跳连接到你 Go 服务的代理的 IP/网段 “:

  • 如果 nginx 和 Go 同机,并且走 127.0.0.1: ,把 127.0.0.0/8(以及可能的 ::1/128)作为 trusted
  • 如果 nginx 在内网: 把 nginx 的内网网段或具体 IP 加进去
  • 如果你的 Go 前面还有一层 Envoy/Ingress: 也要把那层的网段加进去(因为 Go 的 RemoteAddr 看到的是它)

最佳实践:尽量填具体的代理网段/地址,不要偷懒 “ 信任所有内网 “,除非你很确定网络边界干净。

4. IP 地理定位库

获取 IP 后,可通过 GeoIP 库解析地理位置。推荐使用 MaxMind 的 GeoLite2 数据库:

  • City 库:精度更高,包含城市级别信息
  • Country 库:仅国家级别

资源地址:GeoLite.mmdb 镜像