最安全的客户端IP获取方式是优先使用$_SERVER['REMOTE_ADDR'],仅在明确配置可信代理且REMOTE_ADDR属其网段时,才从X-Forwarded-For中取最后一个合法非私有IP。

直接用 $_SERVER['REMOTE_ADDR'] 是最安全的起点,其他来源(如 HTTP_X_FORWARDED_FOR、HTTP_X_REAL_IP)默认不可信,必须显式验证且仅在可信代理链下才可使用。
为什么不能直接读 HTTP_X_FORWARDED_FOR
这个头由客户端或中间代理注入,完全可控。攻击者只需发一个 curl -H "X-Forwarded-For: 1.2.3.4, 127.0.0.1" ... 就能伪造任意 IP,甚至绕过基于 IP 的限流或封禁逻辑。
- 它本质是字符串列表(逗号分隔),首个 IP 不一定真实,末尾也不一定可靠
- Nginx / Apache 默认不自动清洗该字段,PHP 层无任何校验机制
- 若你的服务暴露在公网且未部署可信反向代理,该字段应被彻底忽略
只在明确有可信代理时才启用多级 IP 解析
前提是:你控制了入口层(如 Nginx 或 Cloudflare),并配置了固定可信代理 IP 段(如 10.0.0.0/8、192.168.0.0/16 或 Cloudflare 的官方 IP 段),且已用 set_real_ip_from + real_ip_header 正确配置。
- 检查
$_SERVER['REMOTE_ADDR']是否落在你声明的可信代理网段内 - 只取
HTTP_X_FORWARDED_FOR中“最后一个非私有、非保留、格式合法”的 IP —— 注意不是第一个,也不是最后一个(后者常是攻击者伪造的) - 必须逐段验证:用
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) - 若没有可信代理,跳过所有头字段,只返回
$_SERVER['REMOTE_ADDR']
get_client_ip() 函数该怎么写才靠谱
下面是一个最小可行实现,不含框架依赖,不假设部署环境:
立即学习“PHP免费学习笔记(深入)”;
function get_client_ip(): ?string
{
$remote = $_SERVER['REMOTE_ADDR'] ?? null;
if (!$remote || !filter_var($remote, FILTER_VALIDATE_IP)) {
return null;
}
<pre class="brush:php;toolbar:false;">// 只有当你确认 Nginx/Apache 已配置可信代理,且 REMOTE_ADDR 来自其中时,才继续
$trustedProxies = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.1'];
$isTrusted = false;
foreach ($trustedProxies as $cidr) {
if (filter_var($remote, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && ip_in_cidr($remote, $cidr)) {
$isTrusted = true;
break;
}
}
if (!$isTrusted) {
return $remote; // 拒绝信任任何 HTTP 头
}
$xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
if (!$xff) {
return $remote;
}
$ips = array_map('trim', explode(',', $xff));
for ($i = count($ips) - 1; $i >= 0; $i--) {
$ip = $ips[$i];
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
return $remote;}
// 简单 CIDR 匹配(IPv4) function ip_in_cidr(string $ip, string $cidr): bool { [$net, $bits] = explode('/', $cidr); $ipLong = ip2long($ip); $netLong = ip2long($net); $mask = -1
关键点:不硬编码 Cloudflare 或某厂商逻辑;不信任 HTTP_X_REAL_IP(它同样可被伪造,除非你在 Nginx 里用 set_real_ip_from 显式设为可信源);不尝试“智能合并”多个头字段。
真正容易被忽略的是:很多团队把代理配置写在文档里,但没同步更新 PHP 的 $trustedProxies 列表;或者换了 CDN 后忘记调整 CIDR 范围,结果导致生产环境突然开始记录错误 IP。IP 获取不是一次配置,而是和基础设施强绑定的持续对齐动作。











