引言:为什么“仅仅使用REMOTE_ADDR”已经行不通了
每位PHP开发者都曾至少问过这样一个问题:“如何获取客户端IP地址?”在Stack Overflow上,这个话题积累了数十万次浏览,而标准答案下也有几十条补充评论。问题在于,到了2025年,仅仅使用$_SERVER['REMOTE_ADDR']已经不够了。
现代网站运行在CDN(Cloudflare、Akamai)、负载均衡器和代理服务器之后。用户通过VPN或Tor上网。而攻击者已经学会了伪造请求头。如果你想了解访问者的真实IP地址——同时又不给攻击敞开大门——就需要采用经过验证的实践方法。
在本文中,我将展示PHP中IP识别的工作原理,分析其中的陷阱,并提供可在2025年放心使用的现成代码。
PHP如何“看到”IP地址?
当浏览器向服务器发送请求时,PHP会自动填充超全局数组$_SERVER。其中,键REMOTE_ADDR负责存储“通信对方”的IP地址。在理想情况下(没有代理),这就是客户端的IP:
<?php$ip = $_SERVER['REMOTE_ADDR']; // 192.168.1.5?>但在现实中,服务器后面通常有nginx、HAProxy或Cloudflare。此时REMOTE_ADDR显示的是代理的IP,而非用户的IP。真实地址通过HTTP请求头传递:
HTTP_X_FORWARDED_FOR— 代理的标准请求头;HTTP_X_REAL_IP— nginx使用的请求头;HTTP_CF_CONNECTING_IP— Cloudflare专用的请求头;HTTP_CLIENT_IP— 较少见,但也会遇到。
问题在于:这些请求头中的任何一个都可能被伪造。如果你不加验证地信任$_SERVER['HTTP_X_FORWARDED_FOR'],攻击者可以轻易地伪造一个他人的IP。
REMOTE_ADDR vs X-Forwarded-For:何时使用哪个?
以下是一个关键规则,能帮你避免头痛:
- 如果网站直接部署在服务器上,没有代理 — 仅使用
REMOTE_ADDR。 - 如果网站位于受信任的代理(CDN、负载均衡器)之后 — 从请求头中获取IP,但前提是必须验证请求确实来自你的代理。
在2025年,事实上的标准是X-Forwarded-For。其中可能包含一个IP链:client, proxy1, proxy2。第一个地址是客户端地址,最后一个地址是最后一个代理的地址。
为什么不能盲目信任X-Forwarded-For?
想象一下:攻击者发送一个带有X-Forwarded-For: 127.0.0.1请求头的请求。如果你的代码只是简单地读取$_SERVER['HTTP_X_FORWARDED_FOR'],你会得到127.0.0.1(本地主机)。日志、分析、地理位置——全都会出错。更糟糕的是,攻击者可以伪造另一个用户的IP来冒充他。
解决方案:仅当REMOTE_ADDR等于你受信任代理的IP时,才信任X-Forwarded-For请求头。别无他法。
2025年最佳实践:通用的IP获取函数
我整理了一个经过验证的逻辑,它考虑了所有现代实际情况。该函数:
- 检查请求是否通过受信任的代理(由你指定列表)。
- 如果是,则从
X-Forwarded-For中提取第一个IP。 - 如果不是,则返回
REMOTE_ADDR。 - 过滤私有和保留地址(可选)。
<?php/** * 获取客户端的真实IP地址。 * * @param array $trustedProxies 受信任代理IP列表 * @return string IP地址,如果无法确定则返回'0.0.0.0' */function getClientIP(array $trustedProxies = []): string { $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';