本文目录导读:

在 PHP 项目中,获取用户的真实 IP 地址(而不是代理或 CDN 的 IP)需要处理多种代理场景,同时要警惕 IP 伪造攻击,以下是安全且有效的实现方案。
核心原理:HTTP 头部字段
用户的真实 IP 通常携带在以下 HTTP 头部(按可靠程度降序):
| 头部字段 | 说明 | 可靠性 |
|---|---|---|
REMOTE_ADDR |
TCP 连接对端 IP(PHP 原生变量) | ⭐⭐⭐⭐⭐ 最可靠,但可能是代理 IP |
HTTP_X_FORWARDED_FOR |
标准代理转发头,格式:client, proxy1, proxy2 |
⭐⭐⭐ 可伪造,需验证 |
HTTP_X_REAL_IP |
Nginx 等常用,一般只有单个 IP | ⭐⭐⭐⭐ 较可靠(需信任代理) |
HTTP_CLIENT_IP |
较少用,部分老旧系统 | ⭐⭐ 易伪造 |
HTTP_X_CLUSTER_CLIENT_IP |
某些负载均衡器使用 |
安全可靠的实现代码
<?php
/**
* 获取用户真实 IP 地址(安全版本)
*
* @param array $trustedProxies 信任的代理 IP 列表(可选)
* @return string IP 地址或 '0.0.0.0'
*/
function getRealIP($trustedProxies = []) {
$ip = '0.0.0.0';
// 标准代理链头部(最常用)
$forwardedFor = getHeader('HTTP_X_FORWARDED_FOR');
if (!empty($forwardedFor)) {
// 提取第一个非信任代理的 IP(最左边通常是用户真实 IP)
$ips = explode(',', $forwardedFor);
$ips = array_map('trim', $ips);
// 如果设置了信任代理列表,则跳过信任的代理 IP
if (!empty($trustedProxies)) {
foreach ($ips as $probeIp) {
if (!in_array($probeIp, $trustedProxies, true)) {
$ip = $probeIp;
break;
}
}
} else {
// 没有信任列表时,默认取第一个(假设最左边是用户)
$ip = $ips[0];
}
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
// Nginx 等常用头部
$realIp = getHeader('HTTP_X_REAL_IP');
if (!empty($realIp) && filter_var($realIp, FILTER_VALIDATE_IP)) {
// 仅在信任列表中有该代理时才使用,否则忽略(防止伪造)
if (empty($trustedProxies) || in_array($_SERVER['REMOTE_ADDR'] ?? '', $trustedProxies, true)) {
return $realIp;
}
}
// 部分负载均衡器使用
$clientIp = getHeader('HTTP_CLIENT_IP');
if (!empty($clientIp) && filter_var($clientIp, FILTER_VALIDATE_IP)) {
if (empty($trustedProxies) || in_array($_SERVER['REMOTE_ADDR'] ?? '', $trustedProxies, true)) {
return $clientIp;
}
}
// 最后回退到 TCP 连接 IP(最可靠但可能是代理本身)
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
if (filter_var($remoteAddr, FILTER_VALIDATE_IP)) {
return $remoteAddr;
}
return '0.0.0.0';
}
/**
* 安全获取 HTTP 头部(兼容 CLI 模式)
*/
function getHeader($key) {
if (PHP_SAPI === 'cli') {
return '';
}
return $_SERVER[$key] ?? '';
}
// ===== 使用示例 =====
// 1. 基础使用
$userIp = getRealIP();
echo "用户 IP: " . $userIp;
// 2. 设置信任的代理 IP(推荐在部署环境配置)
$trustedProxies = [
'10.0.0.1', // 内网反向代理
'203.0.113.10', // 云 CDN 节点
'198.51.100.0/24' // CIDR 格式(需额外处理)
];
$userIp = getRealIP($trustedProxies);
// 3. 获取国别信息(需要 IP 地理信息库)
$country = getCountryFromIP($userIp); // 自行实现
针对不同代理场景的配置
场景 1:CDN(CloudFlare、阿里云 CDN 等)
// CloudFlare 会发送真实 IP 在某些头部
function getCloudFlareIP() {
// CF-Connecting-IP 是 CloudFlare 专用头部
$cfIp = $_SERVER['HTTP_CF_CONNECTING_IP'] ?? null;
if ($cfIp && filter_var($cfIp, FILTER_VALIDATE_IP)) {
return $cfIp;
}
return getRealIP(); // 回退通用方法
}
场景 2:Nginx 反向代理
Nginx 配置:
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://backend;
}
PHP 端:
// 当 Nginx 在同一台机器时,REMOTE_ADDR 可能是 127.0.0.1 // 需要信任本地代理 $trustedProxies = ['127.0.0.1', '::1']; $userIp = getRealIP($trustedProxies);
场景 3:AWS ELB / ALB
// AWS 负载均衡会在 X-Forwarded-For 中传递真实 IP // 通常只需取第一个 IP $ip = getRealIP(['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']); // 内网信任
IP 地址验证与清洗
/**
* 验证并清洗 IP 地址
*/
function validateIP($ip) {
// 过滤私有 IP(可选)
$privateRanges = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8',
'::1/128',
'fc00::/7'
];
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
return false;
}
// 检查是否为私有 IP
foreach ($privateRanges as $range) {
if (ipInRange($ip, $range)) {
return false; // 不信任私有 IP 作为用户 IP
}
}
return $ip;
}
/**
* CIDR 范围检查(简化版)
*/
function ipInRange($ip, $range) {
list($subnet, $bits) = explode('/', $range);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// IPv6 处理需要额外逻辑
return false;
}
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask;
return ($ip & $mask) === $subnet;
}
生产环境最佳实践
不要完全信任 X-Forwarded-For
- 不使用信任列表时:X-Forwarded-For 可以伪造,应配合 REMOTE_ADDR 使用
- 建议方案:使用
REMOTE_ADDR作为主要判断,只有在 REMOTE_ADDR 是信任代理时才转用 X-Forwarded-For
框架集成示例(Laravel)
// config/trustedproxies.php
return [
'proxies' => '*', // 信任所有代理(仅开发环境)
// 生产环境配置具体 IP
'proxies' => [
'192.168.1.1',
'10.0.0.0/8',
],
'headers' => [
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO,
],
];
// Controller 中
$ip = $request->ip(); // Laravel 会自动处理
日志记录建议
// 记录所有相关头部以便审计
$logData = [
'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? '',
'x_forwarded_for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '',
'x_real_ip' => $_SERVER['HTTP_X_REAL_IP'] ?? '',
'final_ip' => getRealIP(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'timestamp' => date('Y-m-d H:i:s'),
];
error_log(json_encode($logData));
安全警告
- 不要将获取的 IP 直接用于安全认证(如登录验证),IP 可能被伪造
- 不建议依赖 IP 进行敏感操作(如支付验证),应配合 Session/Token
- 生产环境应配置所有代理的信任列表,避免被攻击者伪造 IP
完整工具类
<?php
class IPUtils {
/**
* 获取真实 IP 地址
*/
public static function getRealIP(array $trustedProxies = []): string {
$server = $_SERVER;
// 1. 检查 CloudFlare 专用头部
if (!empty($server['HTTP_CF_CONNECTING_IP'])) {
$ip = $server['HTTP_CF_CONNECTING_IP'];
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
// 2. 检查 X-Forwarded-For(仅当 REMOTE_ADDR 是信任代理时)
$remoteAddr = $server['REMOTE_ADDR'] ?? '0.0.0.0';
$isTrusted = empty($trustedProxies) || self::isTrustedProxy($remoteAddr, $trustedProxies);
if ($isTrusted && !empty($server['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $server['HTTP_X_FORWARDED_FOR']);
$ips = array_map('trim', $ips);
// 从右向左找到第一个非信任的 IP(标准代理链顺序)
foreach (array_reverse($ips) as $ip) {
if (filter_var($ip, FILTER_VALIDATE_IP) && !self::isTrustedProxy($ip, $trustedProxies)) {
return $ip;
}
}
// 所有 IP 都是信任代理,返回最后一个(可能也是代理)
if (!empty($ips) && filter_var($ips[0], FILTER_VALIDATE_IP)) {
return $ips[0];
}
}
// 3. 检查 X-Real-IP(仅当信任代理时)
if ($isTrusted && !empty($server['HTTP_X_REAL_IP'])) {
$ip = $server['HTTP_X_REAL_IP'];
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
// 4. 回退到 REMOTE_ADDR
if (filter_var($remoteAddr, FILTER_VALIDATE_IP)) {
return $remoteAddr;
}
return '0.0.0.0';
}
/**
* 检查 IP 是否在信任列表中
*/
private static function isTrustedProxy(string $ip, array $trustedProxies): bool {
foreach ($trustedProxies as $trusted) {
// 支持 CIDR 范围
if (strpos($trusted, '/') !== false) {
if (self::ipInCIDR($ip, $trusted)) {
return true;
}
} elseif ($ip === $trusted) {
return true;
}
}
return false;
}
private static function ipInCIDR(string $ip, string $cidr): bool {
list($subnet, $mask) = explode('/', $cidr);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// IPv6 CIDR 检查(简化)
return false;
}
$ipLong = ip2long($ip);
$subnetLong = ip2long($subnet);
$maskLong = -1 << (32 - (int)$mask);
$subnetLong &= $maskLong;
return ($ipLong & $maskLong) === $subnetLong;
}
}
使用:
$ip = IPUtils::getRealIP(['10.0.0.0/8', '172.16.0.0/12']); echo "用户真实 IP: " . htmlspecialchars($ip);
真正的 IP 获取没有银弹,需要根据部署架构合理配置 信任代理列表,对于大多数标准场景,使用 X-Forwarded-For 头部加 REMOTE_ADDR 回退即可满足需求,务必在生产环境配置好代理信任白名单,防止 IP 伪造攻击。