本文目录导读:

在 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 伪造攻击。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播