本文目录导读:

在PHP项目中精准获取用户真实IP地址需要考虑多种因素,因为存在代理、CDN、负载均衡等情况,以下是一个完整可靠的解决方案:
核心获取函数
<?php
/**
* 精准获取用户真实IP地址
* @return string 用户IP地址
*/
function getRealIp() {
// 优先级从高到低检查各种HTTP头
$ipHeaders = [
'HTTP_X_FORWARDED_FOR', // 标准代理转发
'HTTP_X_REAL_IP', // Nginx等服务器
'HTTP_CLIENT_IP', // 部分代理
'HTTP_X_FORWARDED', // 通用转发
'HTTP_FORWARDED_FOR', // RFC标准
'HTTP_FORWARDED', // RFC标准
'REMOTE_ADDR' // 最后备选(TCP连接IP)
];
foreach ($ipHeaders as $header) {
if (isset($_SERVER[$header])) {
$ip = $_SERVER[$header];
// 处理多个IP的情况(如X-Forwarded-For: client, proxy1, proxy2)
if (strpos($ip, ',') !== false) {
$ips = explode(',', $ip);
$ip = trim($ips[0]); // 取第一个(最原始的客户端IP)
}
// 验证IP格式
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
// 如果不要求排除私有IP,改用简单验证
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
// 最终备选
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
增强版(支持IPv4/IPv6)
<?php
/**
* 增强版IP获取,支持IPv6和安全过滤
*/
function getClientIp($trustProxy = false) {
// 如果不信任代理,直接返回TCP连接IP
if (!$trustProxy) {
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
$ip = null;
// 优先检查标准代理头
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$forwardedIps = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
foreach ($forwardedIps as $forwardedIp) {
$forwardedIp = trim($forwardedIp);
// 跳过私有IP和保留IP
if (filter_var($forwardedIp, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $forwardedIp;
break;
}
}
}
// 其他代理头
if (!$ip && isset($_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
if (!$ip && isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}
// 验证最终IP
if ($ip && !filter_var($ip, FILTER_VALIDATE_IP)) {
$ip = null;
}
return $ip ?? ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0');
}
使用示例
<?php
// 基本使用
$userIp = getRealIp();
echo "用户IP: " . $userIp;
// 增强版(信任代理)
$clientIp = getClientIp(true);
// 转换为整数(用于存储)
$ipLong = ip2long($userIp);
// 获取IP地理位置(需要第三方服务)
function getIpLocation($ip) {
$url = "http://ip-api.com/json/{$ip}";
$response = @file_get_contents($url);
if ($response) {
$data = json_decode($response, true);
return $data['country'] . ' ' . ($data['city'] ?? '');
}
return 'Unknown';
}
注意事项
安全考虑
- IP伪造风险:
X-Forwarded-For等头可以被伪造 - 建议:只在可信网络环境(如内部网络、CDN后)信任代理头
- 敏感操作:如登录、支付等,建议使用
REMOTE_ADDR
不同环境配置
Nginx配置(推荐):
# 在location或server块中 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
Apache配置:
# 在httpd.conf或.htaccess RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
特殊情况处理
CDN环境(CloudFlare、Akamai等):
function getCfIp() {
// CloudFlare使用CF-Connecting-IP
if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
return getRealIp();
}
阿里云/腾讯云CDN:
// 可能需要检查特定头 // 阿里云:Ali-CDN-Real-IP // 腾讯云:X-Real-IP
完整实现(生产建议)
<?php
class IpHelper {
private static $trustedProxies = [];
/**
* 设置信任的代理IP列表
*/
public static function setTrustedProxies(array $ips) {
self::$trustedProxies = $ips;
}
/**
* 获取用户真实IP
*/
public static function getIp() {
// 检查是否来自信任代理
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
if (!self::isTrustedProxy($remoteAddr)) {
return $remoteAddr ?: '0.0.0.0';
}
// 从代理头获取
return self::getIpFromHeaders();
}
private static function isTrustedProxy($ip) {
if (empty(self::$trustedProxies)) {
return false;
}
return in_array($ip, self::$trustedProxies);
}
private static function getIpFromHeaders() {
$headers = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'HTTP_CLIENT_IP'
];
foreach ($headers as $header) {
if (isset($_SERVER[$header])) {
$ip = $_SERVER[$header];
if (strpos($ip, ',') !== false) {
$ip = trim(explode(',', $ip)[0]);
}
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
}
// 使用示例
IpHelper::setTrustedProxies(['127.0.0.1', '::1', '10.0.0.0/8']);
$userIp = IpHelper::getIp();
总结建议
- 生产环境:推荐使用Nginx/Apache在服务器层面设置真实IP头
- 安全优先:对敏感操作使用
REMOTE_ADDR - 性能考虑:避免每次请求都调用外部IP查询服务
- 数据存储:使用
ip2long()转整数存储,节省空间 - 日志记录:记录原始
REMOTE_ADDR和代理头信息以便排查