thinkphp5如何获得访客IP?nginx传递ip的注意事项
发布于 作者:苏南大叔 来源:程序如此灵动~
本文也是一篇水文,不过苏南大叔会稍稍给它写出一些深度来。文章中首先要说明一下thinkphp中获得ip的方法,然后描述一下通用的获得ip的方法及其漏洞,最后描述一下nginx和php配合使用的时候,获取ip的注意事项。

本文测试环境:mac/php72/nginx/thinkphp@5.0.24。
thinkphp5获得访客IP
thinkphp5自带的获得ip的代码,非常简单:
首先引入Request类包:
use think\Request;然后初始化Request实例后,调用ip方法获得ip。
$request = Request::instance();
$ip=$request->ip();源文件的位置在thinkphp\library\think\Request.php:
public function ip($type = 0, $adv = true)
{
$type = $type ? 1 : 0;
static $ip = null;
if (null !== $ip) {
return $ip[$type];
}
$httpAgentIp = Config::get('http_agent_ip');
if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) {
$ip = $_SERVER[$httpAgentIp];
} elseif ($adv) {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown', $arr);
if (false !== $pos) {
unset($arr[$pos]);
}
$ip = trim(current($arr));
} elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u", ip2long($ip));
$ip = $long ? [$ip, $long] : ['0.0.0.0', 0];
return $ip[$type];
}源码的逻辑内容,大家自行理解。不过值得注意的是:上面这段thinkphp的代码,可以获取隐藏在自定义header头中的ip信息,这个思路看起来是很新颖不错,点赞。

thinkphp3系列中,获得ip就一个函数,更加简单,get_client_ip()。
网络流传的获取ip的函数
下面的php代码,是网络流传的获得ip的php函数。大家仔细看逻辑,就可以看到相关的漏洞所在。
function get_ip(){
if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
$cip = $_SERVER["HTTP_CLIENT_IP"];
} elseif (!empty($_SERVER["HTTP_X_FORWARDED_FOR"])) {
$cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
} elseif (!empty($_SERVER["REMOTE_ADDR"])) {
$cip = $_SERVER["REMOTE_ADDR"];
} else {
$cip = "--";
}
return $cip;
}大家可以看到,这个ip的获取,是有else和if的,而其中的HTTP_X_FORWARDED_FOR,这个就是漏洞所在。如果相关的脚本,是去先检测HTTP_X_FORWARDED_FOR的话,那么就有一定的概率可以伪造ip了。这里,就涉及到匿名代理和透明代码的相关知识了,这里不做赘述。
thinkphp@3系列中的get_client_ip()中,也考虑到上述问题。下面是其定义:
/**
* 获取客户端IP地址
* @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
* @param boolean $adv 是否进行高级模式获取(有可能被伪装)
* @return mixed
*/
function get_client_ip($type = 0,$adv=false) {
$type = $type ? 1 : 0;
static $ip = NULL;
if ($ip !== NULL) return $ip[$type];
if($adv){
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown',$arr);
if(false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
}elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u",ip2long($ip));
$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
return $ip[$type];
}配合nginx使用
众所周知,大多数时候,线上环境下,都是配合nginx来使用php的。而nginx对于php来说,就是个代理。所以,如果配置不当的情况下,就会恒定取到本机ip作为客户ip,这个就是很令人崩溃的事情了。
不过,这种情况,就需要nginx和php共同协商决定ip的判断标准了。
nginx配置ip:
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
proxy_pass http://backend;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}这里的X-Real-IP,就是目标header。
php获得ip:
if (isset($_SERVER[`X-Real-IP`])) {
$ip = $_SERVER[`X-Real-IP`];
}总结
本文中,苏南大叔并没有推荐任何一种获取ip的方式,大家各自根据实际情况做出选择吧。
ip对于个人来说,就是一串数字罢了。但是在现实的网络环境中,ip是个集合,它包含了各个层面上的代理的结果。所以,对于获得ip这件事情来说,还是仔细思量一下比较好。因为ip的判断标准过于多样,那么这些取ip的逻辑,基本上都可能会存在着些漏洞。所以,使用这些函数方法前,请仔细想清楚。
下面的链接,是苏南大叔写的有关thinkphp的相关文章,敬请惠存: