php获取当前ip-PHP获取客户端真实IP

这两个值都代表客户端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;
    }

结语

简单的方式,后面就是无数的兼容逻辑。