这两个值都代表客户端IP,但可能是代理IPphp获取当前ip,所以优先级较低
如果我们确定给nginx添加自定义header的话,要注意nginx反向代理proxy_set_header的自定义header无效的问题。 关于这一点,我在另一篇文章中详细阐述过。
PHP获取真实IP的方式是因为HTTP_X_FORWARDED_FOR是可以伪造的,这里我们需要对filtered字段进行array_reverse,因为这样获取的IP是忽略伪造的头部的真实客户端IP。 例如:curl中常见的伪造请求头:
$header = array('客户端IP:127.0.0.1', 'X-FORWARDED-FOR:127.0.0.2,127.0.0.3');
如果用户的真实IP是127.0.0.4,那么我们得到的HTTP_X_FORWARDED_FOR是:
127.0.0.2、127.0.0.3、127.0.0.4
如果我们使用代理,这里需要过滤代理IPphp获取当前ip,避免反向后命中我们自己的代理IP
3.注意:我这里没有做上述处理,因为运维严禁在入口处伪造请求头。 HTTP_X_FORWARDED_FOR是可信的,不能代表所有业务场景
/**
* 获取ip
* @return mixed
*/
public function getIp(){
$client_ip='';
if (isset($_SERVER))
{
if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
{
//优先使用 HTTP_X_FORWARDED_FOR,此值是一个逗号分割的多个IP
//注意:我这里没做处理,是因为运维在入口处禁止了伪造请求头,HTTP_X_FORWARDED_FOR是可信的,不能代表所有业务场景
//todo 没有禁止伪造请求头下的特殊处理
$ipStr = $_SERVER["HTTP_X_FORWARDED_FOR"];
$ipArr = explode(',',$ipStr);
$client_ip = isset($ipArr[0])?$ipArr[0]:'';
}
else if (isset($_SERVER["HTTP_CLIENT_IP"]))
{
$client_ip = $_SERVER["HTTP_CLIENT_IP"];
}
else
{
$client_ip = $_SERVER["REMOTE_ADDR"];
}
}
//过滤无效IP
if(filter_var($client_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false || filter_var($client_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false){
return $client_ip;
}else{
return $_SERVER["REMOTE_ADDR"];
}
}
Laravel 获得真实 IP
Laravel 社区的 Summer 3 年前就说过可以使用 setTrustedHeaderName() 来设置。 现在这个方法早已被setTrustedProxies所取代。
我们来看看新方法做了什么
/*方法接受两个参数.
array proxies,接收我们信任的代理IP数组,这些IP在获取时会被过滤。
int trustedHeaderSet获取信任的HeaderSet
这里是兼容了一些无法返回HEADER_X_FORWARDED_FOR的服务器,
例如AWS,或者符合RFC 7239标准。*/
public static function setTrustedProxies(array $proxies, int $trustedHeaderSet)
{
//设置trustedProxies为处理过的代理,字符串REMOTE_ADDR做特殊处理
self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) {
//将所传代理放置到信任代理中
if ('REMOTE_ADDR' !== $proxy) {
$proxies[] = $proxy;
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
//如果所传参数是字符串“REMOTE_ADDR”,则将当前的REMOTE_ADDR添加到信任代理中
$proxies[] = $_SERVER['REMOTE_ADDR'];
}
return $proxies;
}, []);
self::$trustedHeaderSet = $trustedHeaderSet;
}
可以看到我们调用这个方法的参数可以写我们信任的IP,或者传递字符串REMOTE_ADDR。 如果传递REMOTE_ADDR,则当前的$_SERVER[REMOTE_ADDR]将被添加到信任代理中
$param = ['REMOTE_ADDR','127.0.0.1'];
request()->setTrustedProxies($param);
那么我们看看执行getClientIp时我们采取了哪些步骤
public function getClientIp()
{
//从IP池取IP,并返回0下标的IP返回
$ipAddresses = $this->getClientIps();
return $ipAddresses[0];
}
public function getClientIps()
{
//从REMOTE_ADDR取IP
$ip = $this->server->get('REMOTE_ADDR');
//判断是否来自信任代理
if (!$this->isFromTrustedProxy()) {
//如果不在设置的信任代理中,那么直接返回REMOTE_ADDR
return [$ip];
}
//这里才会从HEADER_X_FORWARDED_FOR中获取真实IP
return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip];
}
//注意看这里$trustedProxies就是我们设置信任IP的那个方法存储的属性,这里会判断当前的REMOTE_ADDR是否在我们信任的代理池中,如果没设置或者没在,就返回false
public function isFromTrustedProxy()
{
return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
}
粗略地说,我总结一下:
如果没有调用setTrustedProxies方法,或者REMOTE_ADDR的IP不在setTrustedProxies方法设置的IP代理池中,则getClientIp返回REMOTE_ADDR的IP。
如何处理
这个场景很熟悉,所以我们询问运维我们所有的代理IP,通过setTrustedProxies进行设置。
实际应用
那么,从表面上看,当运维给我们发送一个IP段时会发生什么呢? 甘!
还记得微信公众号开发的时候,可信IP地址不支持填写IP段。 我自己写了一个脚本来生成它。
转过去
setTrustedProxies方法的第一个参数有这样的判断:
如果传递的参数是字符串“REMOTE_ADDR”,则将当前的REMOTE_ADDR添加到信任代理中
**setTrustedProxies**方法的第二个参数:获取信任的HeaderSet
那么,我们可以如下设置,默认不信任REMOTE_ADDR的IP,只有在没有获取到真实IP的情况下才使用这个REMOTE_ADDR。
laravel通常如何获取真实IP
/***
不同HEADER的int值
const HEADER_FORWARDED = 0b00001; // When using RFC 7239
const HEADER_X_FORWARDED_FOR = 0b00010;
const HEADER_X_FORWARDED_HOST = 0b00100;
const HEADER_X_FORWARDED_PROTO = 0b01000;
const HEADER_X_FORWARDED_PORT = 0b10000;
const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
***/
public function getIp(){
request()->setTrustedProxies(['REMOTE_ADDR'],0b00010);
$ip = request()->getClientIp();
}
如上所述,我使用 HEADER_X_FORWARDED_FOR 作为我信任的真实 IP 源,如果无法获取,我使用 REMOTE_ADDR。
注意,我这里只使用了REMOTE_ADDR,如果还有其他代理IP,可以将它们添加到链表中。
根据使用场景进行了更改
运维限制了入口,我们不用再担心伪造头的问题了,所以这里我们还是从左边获取IP。 (原来的方法会执行array_reverse)
public function getIp(){
request()->setTrustedProxies(['REMOTE_ADDR'],0b00010);
$ip = array_reverse(request()->getClientIps())[0];
return $ip;
}
结语
简单的方式,后面就是无数的兼容逻辑。
发表评论