本文目录导读:

在PHP项目中,获取经过CDN转发后的真实用户IP,通常需要检查特定的HTTP请求头,CDN(内容分发网络)在转发请求时,会将客户端的原始IP地址添加到一个标准或自定义的请求头中,最常见的头是 X-Forwarded-For。
以下是几种常见的实现方法及最佳实践:
最标准的方法:使用 X-Forwarded-For 头
X-Forwarded-For (XFF) 是事实上的标准,但其格式可能包含多个IP(client_ip, proxy1_ip, proxy2_ip),你需要获取最左边的IP,因为那是最终客户端的IP。
<?php
function getClientRealIP() {
$ip = '';
// 1. 尝试从 X-Forwarded-For 获取(最左边的是真实IP)
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// 可能包含逗号分隔的多个IP,取第一个
$ipList = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = trim($ipList[0]); // 取最左边,即原始客户端IP
}
// 2. X-Forwarded-For 不存在,尝试 X-Real-IP(某些CDN如Nginx常用)
if (empty($ip) && isset($_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
// 3. 最后退回到 REMOTE_ADDR(但此时可能是CDN节点IP而非用户IP)
if (empty($ip)) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
}
// 4. 验证IP格式(防止伪造或无效数据)
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) {
return $ip;
}
// 如果验证失败,返回一个默认值或REMOTE_ADDR
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
// 使用示例
$userIP = getClientRealIP();
echo "用户真实IP: " . $userIP;
?>
推荐的生产环境实践:配置白名单 + 校验
为什么需要白名单? 直接信任 X-Forwarded-For 存在安全隐患:如果攻击者直接访问你的源站(未经过CDN),他们可以伪造 X-Forwarded-For 头来伪装IP。
解决方案: 只在你信任的CDN代理IP之后,才信任 X-Forwarded-For。
<?php
// 定义允许的CDN节点IP或CIDR(需要从你的CDN提供商获取)
$trustedProxies = [
'103.21.244.0/22', // Cloudflare 示例
'103.22.200.0/22',
// ... 实际你需要替换为你使用的CDN(阿里云CDN、腾讯云CDN、Fastly等)
];
// 检查当前 REMOTE_ADDR 是否属于信任代理
function isTrustedProxy($ip) {
global $trustedProxies;
foreach ($trustedProxies as $proxyCidr) {
if (ipInCIDR($ip, $proxyCidr)) {
return true;
}
}
return false;
}
// 简化版CIDR匹配函数(生产环境建议使用库或更完善的实现)
function ipInCIDR($ip, $cidr) {
if (strpos($cidr, '/') === false) {
return $ip === $cidr;
}
list($subnet, $mask) = explode('/', $cidr);
$ipLong = ip2long($ip);
$subnetLong = ip2long($subnet);
$maskLong = -1 << (32 - (int)$mask);
return ($ipLong & $maskLong) === ($subnetLong & $maskLong);
}
function getClientRealIPSafe() {
// 如果请求来自信任的CDN节点,则信任头信息
if (isTrustedProxy($_SERVER['REMOTE_ADDR'])) {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ipList = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$ip = trim($ipList[0]);
} elseif (isset($_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
}
// 默认使用 REMOTE_ADDR(可能是CDN节点IP或用户IP)
if (empty($ip)) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
}
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
return '0.0.0.0';
}
// 实际使用
$userIP = getClientRealIPSafe();
?>
常见CDN使用的特殊请求头
除了 X-Forwarded-For,不同CDN可能使用不同的头:
| CDN 服务商 | 常用头 |
|---|---|
| 阿里云 CDN | X-Forwarded-For (标准) 或通过 Ali-CDN-Real-IP (旧版) |
| 腾讯云 CDN | X-Forwarded-For |
| Cloudflare | CF-Connecting-IP (更推荐) 或 X-Forwarded-For |
| Fastly | X-Forwarded-For |
| Amazon CloudFront | CloudFront-Viewer-Address (格式: ip:port) |
| Nginx + CDN 插件 | X-Real-IP, X-Forwarded-For |
你可以针对你使用的CDN增加特定头检查:
// Cloudflare 特别版本
if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
}
// CloudFront 特殊处理
if (isset($_SERVER['HTTP_CLOUDFRONT_VIEWER_ADDRESS'])) {
$ip = explode(':', $_SERVER['HTTP_CLOUDFRONT_VIEWER_ADDRESS'])[0];
}
服务器配置层面的常见方案(推荐优先配置)
在PHP层面处理是有效的,但更好的做法是在Web服务器(Nginx/Apache)层面做一次转换,直接让PHP看到真实IP,例如在 Nginx 的 server 块中添加:
# 在 Nginx 配置中(反向代理部分或全局) set_real_ip_from 103.21.244.0/22; # 信任的CDN IP段 set_real_ip_from 103.22.200.0/22; # ... 其他CDN IP段 real_ip_header X-Forwarded-For; real_ip_recursive on; # 递归处理:当有多个代理时,取最左有效IP
这样配置后,PHP直接使用 $_SERVER['REMOTE_ADDR'] 就能得到真实用户IP,这是最干净、最安全的方案。
总结建议
| 方案 | 适用场景 | 安全性 |
|---|---|---|
| Nginx/Apache 层面配置 (推荐) | 你拥有服务器配置权限 | 高,源头处理 |
| PHP 层面 + IP白名单校验 | 无法修改服务器配置,但能改代码 | 高(配合白名单) |
| PHP 层面直接取 XFF 第一个 | 只有可控内网/测试环境 | 低(易被伪造) |
- 优先配置服务器:如果能修改Nginx/Apache配置,在服务器端设置
real_ip_header和set_real_ip_from是最佳实践。 - 如果必须用PHP:请务必加上信任IP白名单(你CDN节点的IP范围),否则恶意客户端可以直接伪造IP。
- 始终验证IP格式:使用
filter_var(..., FILTER_VALIDATE_IP)过滤脏数据。
通过以上方法,你就能可靠地从PHP项目中获取经过CDN转发的真实用户IP。