PHP项目代理IP如何真实获取地址

wen PHP项目 27

本文目录导读:

PHP项目代理IP如何真实获取地址

  1. 核心原理:HTTP 头部字段
  2. 安全可靠的实现代码
  3. 针对不同代理场景的配置
  4. IP 地址验证与清洗
  5. 生产环境最佳实践
  6. 完整工具类

在 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 伪造攻击。

抱歉,评论功能暂时关闭!