PHP获取IP地址方法及原理 - konakona
konakona
Dream Afar.
konakona

PHP获取IP地址方法及原理

此文属转载,写得不错。但konakona有话要说:php是无法获得代理后的真实ip的,文章中也很好的说了“如果代理商就是不留下任何记号,你始终无法知道它的真实ip”。但我更在意的是ip138和51la获取真实ip的方法!同时它们2个都是asp……

小问题中包含大学问。

在谷歌中搜索“PHP中获取地址的方法”,肯定能得到不少的结果。简单点的,只需要

 

$ip = $_SERVER['REMOTE_ADDR'];

复杂点的,可能就需要判断更多,比如,(来自discuz代码)

if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
	$onlineip = getenv('HTTP_CLIENT_IP');
} elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
	$onlineip = getenv('HTTP_X_FORWARDED_FOR');
} elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
	$onlineip = getenv('REMOTE_ADDR');
} elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
	$onlineip = $_SERVER['REMOTE_ADDR'];
}

那么,隐藏在这复杂判断情形之下的原理是什么?为什么要这么判断?现在让我们一一揭开。

其实,浏览器访问PHP服务器时,假设服务器使用Apache,那么Apache会负责在_SERVER变量中设置环境变量。设置的内容遵从CGI/1.1定义(你可以查看http://bitsucker.com/index.php/archives/61获取更多关于CGI/1.1的说明)。那么来看这几个可能遇到的_SERVER变量:

REMOTE_ADDR

The IP address of the agent sending the request to the server. Not necessarily that of the client.对服务器发起访问的代理的IP地址。不一定是客户端的。IP地址遵从格式:REMOTE_ADDR = hostnumber;hostnumber = digits “.” digits “.” digits “.” digits;digits = 1*digit

也就是说,这个存放的是IP地址,但是有可能是用户使用的代理的IP地址,不一定是真实的用户地址。那么直接使用这个变量来获取地址的话,还真不好意思说方法名是getRealIP。那么有没有办法确认代理呢?继续看。

HTTP_*

These variables are specific to requests made with HTTP.Environment variables with names beginning with “HTTP_” contain header data read from the client, if the protocol used was HTTP.The server is not required to create environment variables for all the headers that it receives. In particular, it might remove any headers carrying authentication information, such as `Authorization’.以HTTP_开头的变量描述HTTP请求的具体环境,它包含客户端发送的头信息。但是,不要求服务器设置接收到的所有头信息,服务器可以有选择的取舍。(特别是,服务器可以去掉头信息中还有的验证部分。)

HTTP_X_FORWARDED_FOR,HTTP_CLIENT_IP

这一类的变量(以HTTP_开头的)并不是CGI/1.1规范严格定义的。它们可以有客户端自由定义(,至少根据规范的描述来说)。所幸,如果用户使用代理,一般HTTP_X_FORWARDED_FOR或HTTP_CLIENT_IP变量会被设置;在此种情况下,HTTP_X_FORWARDED_FOR或HTTP_CLIENT_IP变量代表了客户端的真实IP,而REMOTE_ADDR则代表此代理的IP地址。那么一般情况下就可以根据此特征获取用户的真实IP地址以及是否使用了代理。

为什么说一般呢?因为这里还有点问题。第一是用户使用多层代理。此时,REMOTE_ADDR代表最后一个使用的代理的IP,而HTTP_X_FORWARDED_FOR会依次设置为这样的形式:你的真实IP地址、第一层代理IP、第二层代理IP、等等,地址之间使用逗号分隔;此时HTTP_X_FORWARDED_FOR相当于代表了真实的客户端到到达最终的服务器之前经过的所有路径点的IP列表;然后HTTP_CLIENT_IP会被代理设置为客户端的真实IP。(所以,用户只使用一层代理的情况下,HTTP_X_FORWARDED_FOR与HTTP_CLIENT_IP相同只是使用多层代理的特殊情况。)再一个问题是代理的邪恶:代理可以依据自己的喜好标准选择是否设置HTTP_X_FORWARDED_FOR与HTTP_CLIENT_IP,设置成什么样的值,设置全部还是部分。因此代理可以提交完整的设置,提交部分设置的数据,提交伪造的IP地址数据,甚至什么也不设置伪装成没有使用代理。(你可以参考这篇文章了解一下更多的内容。下面摘抄了部分关键内容)

 

 

 

1 没有使用代理。嗯,没有使用代理,就是没有使用代理。
REMOTE_ADDR = 你的 IP
HTTP_VIA = 没数值或不显示
HTTP_X_FORWARDED_FOR = 没数值或不显示

2 使用透明代理(Transparent Proxies)。这类代理服务器将请求转发给要访问的对象,不会隐藏自己的真实身份。
REMOTE_ADDR = 最后一个代理服务器 IP
HTTP_VIA = 代理服务器 IP
HTTP_X_FORWARDED_FOR = 你的真实 IP。经过多个代理服务器时,这个值类似于:203.98.182.163, 203.98.182.163, 203.129.72.215

3 使用普通匿名代理(Anonymous Proxies)。它会隐藏你的真实IP,但是向访问对象透露了正在使用代理服务器访问。
REMOTE_ADDR = 最后一个代理服务器 IP
HTTP_VIA = 代理服务器 IP
HTTP_X_FORWARDED_FOR = 代理服务器 IP ,经过多个代理服务器时,这个值类似如下:203.98.182.163, 203.98.182.163, 203.129.72.215

4 使用欺骗性代理(Distorting Proxies)。告诉访问对象您使用了代理服务器,但编造了一个虚假的随机IP代替你的真实IP欺骗它。
REMOTE_ADDR = 代理服务器 IP
HTTP_VIA = 代理服务器 IP
HTTP_X_FORWARDED_FOR = 随机的 IP,经过多个代理服务器时,这个值类似如下:203.98.182.163, 203.98.182.163, 203.129.72.215

5 使用高级匿名代理(High Anonymity Proxies (Elite proxies))。完全用代理服务器的信息替代了你的所有信息,就象你使用那台代理服务器直接访问对象一样。
REMOTE_ADDR = 代理服务器 IP
HTTP_VIA = 没数值或不显示
HTTP_X_FORWARDED_FOR = 没数值或不显示

总结起来,

REMOTE_ADDR 是你的客户端跟服务器“握手”时候的IP。如果使用代理,REMOTE_ADDR将显示代理服务器的IP。
HTTP_CLIENT_IP 是代理服务器设置发送的HTTP头,代表你的客户端的真实IP。当然,人家可以选择设置与否以及设置成什么样。
HTTP_X_FORWARDED_FOR 是用户到达最终访问地时经过的路径访问点(包括自身和使用的多层代理)的IP列表。依然取决于代理的设置,可以伪造。

知道了以上的情况,我们就可以写出比较完整的获取IP地址的方法。但是鉴于IP地址还是有可能被某些代理伪造,所以叫做getRealIP还是稍显底气不足。

 

//一个注释详细的方法
function getRealIP()
{
    static $realIP;

    if($realIP) return $realIP;
    //php内部函数getenv的参数不区分大小写,所以用起来还是比较方便的
    //首先看看有没有设置HTTP_CLIENT_IP
    if($ip = getenv('HTTP_CLIENT_IP'))
    {
        //什么也不做。当然这个if判断可以直接和下面的elseif合并,只是稍显凌乱
        //if(($ip = getenv('HTTP_CLIENT_IP')) || ($ip = getenv('HTTP_X_FORWARDED_FOR')))
    }
    //然后查看IP路径列表
    else if($ip = getenv('HTTP_X_FORWARDED_FOR'))
    {
        $tmps = explode(',', $ip);
        $ip = '';

        //中间有可能是unknown?
        //反正查资料时中文有不少文章都认为可能会出现unknown
        //Discuz更甚,对client_ip及remote_addr都判断是否为unknown
        //可惜我查了许多资料都没有提出这个unknown是怎么回事。英文中反倒是很少见这个判断
        //取X-Forwarded-For中第一个非unknown的有效IP字符串
        foreach ($tmps as $t)
        {
            //服务器有时设置的字符串含有不必要的空白
            $t = trim($t);

            if('unknown' != $t)
            {
                $ip = $t;
                break;
            }
        }
    }
    //此处判断!$ip是由于X-Forwarded-For中可能没有取得有效的IP,比如都是unknown
    //理论上有可能出现这个情况,但是我没有碰到过
    if(!$ip && $ip = getenv('REMOTE_ADDR'))
    {
        //什么也不做。又来一个可以合并的,继续凌乱吧
        //if(!$ip && !($ip = getenv('REMOTE_ADDR')))
    }
    else
    {
        //这里又直接从$_SERVER变量里面取REMOTE_ADDR
        //这是因为getenv不支持IIS的ISAPI方式下运行的PHP
        //ISAPI=Internet Server Application Programming Interface,微软搞出来的
        $ip = !empty($_SERVER['REMOTE_ADDR'])? $_SERVER['REMOTE_ADDR']:'';
    }

    $realIP = preg_match("/[\d\.]{7,15}/", $ip)? $ip:'0.0.0.0';

    return $realIP;
}

 

//一个精简后的方法
function getRealIPs()
{
    static $realIP;
    if($realIP) return $realIP;

    //代理时
    $ip = getenv('HTTP_CLIENT_IP')?  getenv('HTTP_CLIENT_IP'):getenv('HTTP_X_FORWARDED_FOR');
    preg_match("/[\d\.]{7,15}/", $ip, $match);
    if(isset($match[0])) return $realIP = $match[0];

    //非代理时
    $ip = !empty($_SERVER['REMOTE_ADDR'])? $_SERVER['REMOTE_ADDR']:'0.0.0.0';
    preg_match("/[\d\.]{7,15}/", $ip, $match);

    return $realIP = isset($match[0])? $match[0]:'0.0.0.0';
}
赞赏

团哥

文章作者

继续玩我的CODE,让别人说去。 低调,就是这么自信。

konakona

PHP获取IP地址方法及原理
php是无法获得代理后的真实ip的,文章中也很好的说了“如果代理商就是不留下任何记号,你始终无法知道它的真实ip”。但我更在意的是ip138和51la获取真实ip的方法!同时它们2个都是asp……
扫描二维码继续阅读
2011-08-29