从 TCP 连接、HTTP 请求头到代理链追踪,服务器获取客户端真实 IP 的核心方法是通过 socket 连接信息与标准协议头配合解析,具体方案取决于部署架构。
服务器获取客户端ip地址的几种常见方式
服务器想要拿到客户端 IP,首先要明白一件事:你看到的 IP 不一定就是客户端的真实 IP,网络请求经过层层转发后,源地址可能已经被改写或者隐藏了,下面从底层到上层,把常见方法拆开来看。
基于 TCP 连接层的直接获取
当客户端和服务器建立 TCP 连接时,操作系统内核会记录这条连接的两端地址,这是最底层、最真实的获取途径。
在 Python 的 socket 编程里,accept() 返回的第二个参数就是客户端地址:
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8080))
server.listen(5)
while True:
conn, addr = server.accept()
print(f"客户端 IP: {addr[0]}") # 这就是 TCP 层的来源 IP
在 Java 的 Servlet 容器(Tomcat)中,request.getRemoteAddr() 返回的也是这个层面的 IP,这种方式直接且高效,不依赖任何协议头,不会被伪造。
但这个 IP 有一个局限:只要中间隔了代理、负载均衡或 CDN,它拿到的就是上一跳设备的 IP,而不是真实用户客户端的 IP。
基于 HTTP 协议头的间接获取
HTTP 协议在传递过程中,代理服务器会在请求头里附加原始客户端的信息。行业共识认为,X-Forwarded-For 和 X-Real-IP 是当前互联网环境中被接受度最高的两个标准头字段。
X-Forwarded-For:每经过一层代理,就在末尾追加一个 IP,格式是”客户端IP, 代理1, 代理2″。X-Real-IP:Nginx 默认用这个头传递上一级的真实来源 IP,通常由离应用服务器最近的代理层设置。
用 Nginx 做反向代理时,把用户请求转发给后端 Java 应用,Java 代码里这样读取:
String clientIp = request.getHeader("X-Forwarded-For");
if (clientIp == null || clientIp.isEmpty()) {
clientIp = request.getRemoteAddr();
}
注意 X-Forwarded-For 是个逗号分隔的字符串,取第一个 IP 通常才是真实用户,但这要求前面的代理节点正确写入且未被篡改。
代理和负载均衡环境下如何获取客户端真实ip
现代企业架构绕不开负载均衡和 CDN。只要架构中引入了这些中间层,靠 getRemoteAddr() 就一定拿不到真实客户端 IP。
以常见的链路”用户 → CDN → 负载均衡(Nginx) → 应用服务器”为例:
- 用户请求先打到 CDN 节点,CDN 在请求头追加
X-Forwarded-For: 用户IP。 - CDN 把请求转给负载均衡,负载均衡继续追加
X-Forwarded-For: 用户IP, CDN节点IP。 - 应用服务器收到请求,解析
X-Forwarded-For头,取第一个地址即可拿到用户真实 IP。
如果用的是简米云 SLB 或酷番云 CLB,默认会传递 X-Forwarded-For 头,但部分云厂商还会附加自己的头字段,请务必查阅对应云平台的官方文档确认字段名,避免配错。
nginx获取客户端真实ip地址的配置步骤
Nginx 是当前使用率较高的 Web 服务器和反向代理,它的配置直接决定了后端应用拿到的 IP 是否正确。
配置原理与核心指令
Nginx 中有两个关键变量:$remote_addr 和 $proxy_add_x_forwarded_for。
$remote_addr 是 Nginx 与客户端建立 TCP 连接的来源 IP,这个值永远可信但没有穿透代理能力。$proxy_add_x_forwarded_for 则是在用户传入的 X-Forwarded-For 基础上,再追加 $remote_addr 作为新的一跳。
直接暴露公网的场景配置
Nginx 直接对外提供服务,没有其他代理在它前面,那么只需要在 location 块里设置请求头即可:
server {
listen 80;
server_name example.com;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8080;
}
}
把 $remote_addr 直接写入 X-Real-IP,后端读取这个头就能拿到唯一真实来源。
多层代理下的配置方法
当 Nginx 背后还有更高层级的代理时,需要结合 set_real_ip_from 和 real_ip_header 指令,这两个指令属于 ngx_http_realip_module 模块,多数发行版默认已编译,可用 nginx -V 确认。
配置逻辑:告诉 Nginx”哪些 IP 是可信代理”,然后让它从指定的头里提取真实客户端 IP。
# 定义可信的代理节点 IP 或网段 set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; # 从 X-Forwarded-For 中提取最左侧的 IP 作为客户端地址 real_ip_header X-Forwarded-For; real_ip_recursive on;
real_ip_recursive on 表示递归处理,即在可信代理列表中把中间层全部去掉,最终取到最原始的客户端 IP,如果机器前面还有 CDN,要把 CDN 的出口 IP 段也加进 set_real_ip_from。
后端应用读取真实ip的常见配置
PHP 环境(如 Nginx + PHP-FPM 架构)需要在 PHP 中直接读取头信息:
$clientIp = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
Go 语言的 net/http 包不会自动处理这些头,需要手动解析:
func getClientIP(r http.Request) string {
forwarded := r.Header.Get("X-Forwarded-For")
if forwarded != "" {
parts := strings.Split(forwarded, ",")
return strings.TrimSpace(parts[0])
}
return r.RemoteAddr
}
获取客户端ip地址时容易踩的三个坑
看完获取方法,还得知道风险在哪,多数情况下,拿到错误 IP 的根源不是代码写错,而是对网络链路理解不到位。
头信息伪造与安全防护
用户完全可以手动构造请求,在 X-Forwarded-For 头里塞入任意值,如果后端无条件信任这个头,日志和风控系统就会被污染,甚至可能被用来绕过 IP 白名单限制。
防伪造的关键是只信任来自可信代理层的头信息。 应用服务器只接收来自 Nginx 或负载均衡的请求,那么在 Nginx 层面强制覆盖:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
这样用户的构造值会被丢弃,加上当前连接的真实来源后重新写入,从源头杜绝伪造。
IPv6 环境下获取ip的格式问题
IPv6 地址长度为 128 位,格式形如 240e:390:xxxx:xxxx::1,在 X-Forwarded-For 里,IPv6 地址会以原始冒号分隔形式出现,不会自动加方括号,但在 URI 或日志中可能出现方括号包裹的形式,解析时需要考虑兼容。
IPv6 环境中的私网地址(如
fc00::/7 段)和链路本地地址(fe80::/10)不应该被当作公网客户端 IP,判断逻辑中建议同时支持 IPv4 和 IPv6 的格式校验,并过滤内网保留地址。
多级代理链下的 IP 解析顺序
一次请求经过 CDN、WAF、负载均衡三层代理后,X-Forwarded-For 头会积累一串 IP。取最左边还是最右边的 IP,取决于每层代理是否追加了正确信息。
假设链路是”用户 → CDN → WAF → Nginx → 应用”,WAF 如果没有透传而是重置了请求头,Nginx 收到的就只剩”CDN 追加后的一串”,稳妥做法是在每层代理都记录日志,对比实际头部内容,用链路验证代替凭感觉取值。
服务器怎么获取客户端IP地址的常见问题解答
为什么我用 request.getRemoteAddr() 拿到的总是 127.0.0.1
0.0.1 表示请求来自本机回环地址,说明应用和 Nginx 部署在同一台机器上,请求经过本机代理转发后到达应用端口,应用拿到的 TCP 连接来源永远是 Nginx 进程所在的本机地址,这是正常现象,要解决,就必须解析 Nginx 写入的 X-Forwarded-For 头,同时确认 Nginx 阶段已经按上文配置了相关指令。
CDN 加速后的请求,X-Forwarded-For 头为什么有两个 IP
这是正常现象,第一个 IP 是用户真实客户端地址,第二个及之后的 IP 是 CDN 边缘节点的出口地址,只要前面没有伪造直连,取第一个值即可,如果发现第一个值也是内网 IP 或明显的伪造数据,需要检查 CDN 是否开启了回源 IP 透传功能,并确认源站是否只接受 CDN 的回源请求。
服务器获取客户端ip地址的代码写在中间件里还是业务代码里
建议放在中间件或过滤器层统一处理,处理完放进请求上下文(如 ThreadLocal 或 request attribute),业务代码直接取用,这样做的好处是校验逻辑、IPv4/IPv6 兼容处理、伪造防御只写一遍,避免在每个接口里重复解析,如果在业务代码里各自实现,后期调整代理架构时改动的范围和出错概率都会显著变大。
获取客户端 IP 这件事,本质是在多层网络转发中定位到唯一真实来源,核心思路归纳为:底层连接优先,代理头谨慎取,可信层强制覆盖,格式校验兜底,把这四步做扎实,无论是单机部署还是复杂 CDN 链路,都能稳定拿到准确地址。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/762170.html





