这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
网上的获取方法
在获取用户真实IP的时候,在网上搜一下,网上有很多关于PHP获取用户真实IP的方法,例如:
public static function getClientIp()
{
$ip = '';
if ($_SERVER('HTTP_CLIENT_IP')) {
$ip = $_SERVER('HTTP_CLIENT_IP');
}
if ($_SERVER('HTTP_X_REAL_IP')) {
$ip = $_SERVER('HTTP_X_REAL_IP');
} elseif ($_SERVER('HTTP_X_FORWARDED_FOR')) {
$ip = $_SERVER('HTTP_X_FORWARDED_FOR');
$ips = explode(',', $ip);
$ip = $ips[0];
} elseif ($_SERVER('REMOTE_ADDR')) {
$ip = $_SERVER('REMOTE_ADDR');
}
return $ip;
}
看了许多文章基本都是这么写的,但这样获取真的正确吗?
分析参数
上面方法出现了四个参数:HTTP_CLIENT_IP、HTTP_X_REAL_IP、HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR。
现在我们就逐个分析每个参数是干什么的。
1、HTTP_CLIENT_IP和HTTP_X_REAL_IP,都是一个自定义头,目前并不属于任何标准,是代理服务器和 Web服务器之间可以约定用任何自定义头来传递这个信息,可以是这两个,也可以随意写成其他的。
2、HTTP_X_FORWARDED_FOR是一个扩展头。HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP,现在已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用
3、REMOTE_ADDR 是最后一个与服务器握手的IP,是不能伪造的,可能是用户真实IP,可能是用户的代理服务器ip,也可能是自己的反向代理服务器的IP. 主要有以下几种情况:
1)最简单的架构,用户不使用代理服务器,我们也没加代理服务器,这种情况 REMOTE_ADDR就是用户的真实IP:127.168.1.1
2) 用户使用代理服务器,REMOTE_ADDR最终的结果就是用户代理服务器的IP:132.128.1.3
3)用户不使用代理服务器,我们添加的有代理服务器,REMOTE_ADDR最终的结果就是我们的代理服务器的IP:192.168.1.4
4) 用户使用代理服务,我们也添加的有代理服务器,REMOTE_ADDR最终的结果是我们的代理服务器的IP:192.168.1.4
这几个参数怎么来的
1、REMOTE_ADDR是最后一次与服务器通信的ip,是不能伪造了,但不一定是用户的真实IP。
2、HTTP_CLIENT_IP,HTTP_X_REAL_IP,HTTP_X_FORWARDED_FOR属于http请求头信息,主要有两个来源:
第一:用户直接伪造,例如使用postman,在header里直接添加
得到的结果
array (
'HTTP_HOST' => '192.168.197.128',
'HTTP_X_FORWARDED_FOR' => '192.168.0.4,192.168.0.5',
'HTTP_X_REAL_IP' => '192.168.0.3',
'HTTP_CLIENT_IP' => '192.168.0.2',
'REDIRECT_STATUS' => '200',
'SERVER_NAME' => '_',
'SERVER_PORT' => '80',
'SERVER_ADDR' => '192.168.197.128',
'REMOTE_PORT' => '64746',
'REMOTE_ADDR' => '192.168.197.1',
'SERVER_SOFTWARE' => 'nginx/1.14.0',
......
下面省略
)
第二:来自代理服务器的配置,例如这种架构
nginx负载均衡服务器的配置如下:
upstream upstream_name {
server 192.168.0.2;
server 192.168.0.3;
}
server {
listen 80;
server_name _;
location / {
proxy_pass http://upstream_name;
proxy_pass_header Host $host;
proxy_pass_header X-Real-ip $remote_addr;
proxy_pass_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
怎么获取用户真实IP
首先排除用户使用高匿名代理这种情况,因为使用了高匿名代理,我们是获取不到用户真实IP的,如果哪位大神能够在这种情况获取到,还请不吝赐教。
获取用户真实的IP,我们要根据自己的架构情况。
第一种:自己的服务器没有使用任何代理
REMOTE_ADDR是最值得相信的,直接拿这个值就行。
第二种:我们添加了slb,做负载均衡
REMOTE_ADDR就是负载均衡那台服务器的IP了,所以这个值我们不能取。上图这种架构得到的值分两种情况:
1)用户不伪造参数,我们最终得到的值为:
HTTP_X_REAL_IP为127.168.1.1,HTTP_X_FORWARDED_FOR为127.168.1.1。
2)用户伪造参数,X_REAL_IP传47.12.45.78,X_FORWARDED_FOR传47.23.56.89,得到的值为:
HTTP_X_REAL_IP为127.168.1.1,HTTP_X_FORWARDED_FOR为47.23.56.89,127.168.1.1。
结合上面这两种情况,HTTP_X_REAL_IP是用户真实ip,HTTP_X_FORWARDED_FOR取最后一个IP就行,分析到这里,再看看上面的网上的获取方法,感觉网上的方法没毛病,其实不然,我们看下面的第三种架构。
第三种:有些网站为了防止网络攻击添加了高防,就相当于又多了一层代理服务器
最终得到的结果,REMOTE_ADDR为最后一层代理服务器的ip:192.168.1.1,HTTP_X_REAL_IP和HTTP_X_FORWARDED_FOR同样分用户伪造和用户不伪造两种情况:
1)用户不伪造参数,我们最终得到的值为:
HTTP_X_REAL_IP为47.12.45.78,HTTP_X_FORWARDED_FOR为127.168.1.1,47.12.45.78。
2)用户伪造参数,X_REAL_IP传47.12.45.78,X_FORWARDED_FOR传47.23.56.89,得到的值为:
HTTP_X_REAL_IP为47.12.45.78,HTTP_X_FORWARDED_FOR为47.23.56.89,127.168.1.1,47.12.45.78。
通过上面可以看出来,有多层代理时 X_REAL_IP是不对的,HTTP_X_FORWARDED_FOR里用户真实IP的位置是从后往前数=代理层数,从这里看网上的那个方法取第一个明显是不对的。
总结
获取方法还是要结合我们的架构,对自己的架构和使用的方法非常熟悉,才能写出没有bug的代码。