网页设计学习笔记

HTML,CSS,JavaScript

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: :: 管理 ::

在 PHP 中获取用户 IP 地址,首先需要看看 $_SERVER['REMOTE_ADDR'] 变量。使用这个变量在很多情况下是可行的。使用的代码如下:

$realip = $_SERVER['REMOTE_ADDR'];

但是,如果用户通过代理访问网站,或者网站处于反向代理后面(如果网站托管在 AppFog,OpenShift 等云平台上,通常会是这种情形),这个变量的信息就不正确了。这时候,我们需要通过 $_SERVER['HTTP_X_FORWARDED_FOR'] 变量来得到用户的真实 IP 地址。由于代理可能有多个,这个变量是由用多个 IP 地址组成的字符串,各个 IP 地址之间用逗号隔开。 使用的代码如下:

$proxyips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$fromip = trim($proxyips[0]);

如果网站处于反向代理后面(如果网站托管在 AppFog,OpenShift 等云平台上,通常会是这种情形),$_SERVER['REMOTE_ADDR'] 变量一般是私有地址,我们同样需要通过 $_SERVER['HTTP_X_FORWARDED_FOR'] 变量来得到用户的真实 IP 地址。例如,IP 为 222.222.222.222 的用户访问托管在 AppFog,OpenShift 和 SourceForge 这几个云平台的项目时,在 PHP 中得到的来自地址信息。如下:

// appfog
$SERVER['SERVER_ADDR'] = '10.0.3.223'
$SERVER['REMOTE_ADDR'] = '10.0.64.30'
$SERVER['HTTP_X_FORWARDED_FOR'] = '222.222.222.222, 127.0.0.1'
$SERVER['HTTP_X_REAL_FORWARDED_FOR']= '10.0.64.200'

// openshift
$SERVER['SERVER_ADDR'] = '127.3.106.129'
$SERVER['REMOTE_ADDR'] = '127.3.106.129'
$SERVER['HTTP_X_FORWARDED_FOR'] = '222.222.222.222'

// sourceforge
$SERVER['SERVER_ADDR'] = '127.0.0.1'
$SERVER['REMOTE_ADDR'] = '127.0.0.1'
$SERVER['HTTP_X_REMOTE_ADDR'] = '222.222.222.222'

注意 SourceForge 的项目网站使用了反向代理,但却不使用 $_SERVER['HTTP_X_FORWARDED_FOR'] 变量,而是使用了它们特有的 $_SERVER['HTTP_X_REMOTE_ADDR'] 变量。

另外,有些代理或者 ISP 会通过 HTTP_CLIENT_IP 这个变量提供用户的实际 IP 地址。当然还可能有其它的变量,因此要完美地解决这个问题,基本是不可能的。

还有一个更严重的问题需要考虑。除了 REMOTE_ADDR 变量是可靠的,其它的变量都是可以被伪造的。为了避免写入 IP 地址时对数据库的危害,我们需要先验证是否合法的 IP 地址。这个验证过程可以用 PHP 5.2 加入的 filter_var 函数。例如:

if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
    return $ip;
}

其中 FILTER_FLAG_NO_PRIV_RANGE 选项表示排除 10.0.0.0/8,172.16.0.0/12 和 192.168.0.0/16 这三段私有 IP 地址,而 FILTER_FLAG_NO_RES_RANGE 选项表示排除 0.0.0.0/8,169.254.0.0/16,192.0.2.0/24 和 224.0.0.0/4 这几段保留 IP 地址。

我们记录 IP 地址的主要目标是阻止某些用户对资源的滥用或者对网站的攻击。综合上面的种种因素,可以得出较稳妥又简单的方案。首先我们把下列变量得出的 IP 地址按顺序排列如下

$_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REMOTE_ADDR'], $_SERVER['REMOTE_ADDR']

然后我们从中找出两个 IP 地址记录下来:一个是可靠的直接地址,它是从上面列表逆序查找得到的首个合法的公网 IP 地址;第二个是不太可靠的原始地址,它是从上面列表顺序查找得到的首个合法的公网 IP 地址。我们只记录公网 IP 地址,这方便对滥用资源的用户加入黑名单。

从上面也可以看出,PHP 的 filter_var 函数并不会过滤 127.0.0.0/8 地址段,所以我们需要补充这个步骤。最后实现的代码如下:

<?php

$allips = array();

if (isset($_SERVER['HTTP_CLIENT_IP'])) {
  array_push($allips, $_SERVER['HTTP_CLIENT_IP']);
}
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
  $proxyips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
  $allips = array_merge($allips, $proxyips);
}
if (isset($_SERVER['HTTP_X_REMOTE_ADDR'])) {
  array_push($allips, $_SERVER['HTTP_X_REMOTE_ADDR']);
}
array_push($allips, $_SERVER['REMOTE_ADDR']);

echo "<p>allips: " . implode(',', $allips) . "</p>";

function findip($allips) {
  foreach($allips as $ip) {
    $ip = trim($ip);
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
      if (substr($ip, 0, 3) !== '127') {
        return $ip;
      }
    }
  }
  return $_SERVER['REMOTE_ADDR'];
}

$fromip = findip($allips);
$realip = findip(array_reverse($allips));

echo "<p>fromip: $fromip</p>";
echo "<p>realip: $realip</p>";

?>

参考资料:
[1] PHP: $_SERVER - Manual
[2] PHP: filter_var - Manual
[3] PHP: Filter flags - Manual
[4] IPv4 - 维基百科,自由的百科全书
[5] X-Forwarded-For - Wikipedia, the free encyclopedia
[6] retrieve a user's correct IP address in PHP? - Stack Overflow
[7] #2461 Project Web forum assigns all visitors loop back ip of 127.0.0.1

posted on 2013-01-28 16:41  zoho  阅读(495)  评论(0编辑  收藏  举报