如何在PHP中获取客户端IP地址

How to get the client IP address in PHP

提问人:Anup Prakash 提问时间:6/9/2010 最后编辑:Peter MortensenAnup Prakash 更新时间:10/25/2023 访问量:2140465

问:

如何使用PHP获取客户端IP地址?

我想记录通过他/她的IP地址登录我网站的用户。

php 环境变量 ip-address

评论

3赞 Patrick Mevzek 1/4/2018
请参阅RFC6302有关记录内容的建议,特别是现在请记住记录端口,而不仅仅是地址。
3赞 Jonathan DS 8/8/2018
对于那些跟踪用户的人来说,需要注意的是,在全球的几个地区,ISPS正在使用CGNAT,这使得仅仅信任IP地址变得更加复杂
2赞 4/5/2020
函数 getUserIpAddr(){ if(!empty($_SERVER['HTTP_CLIENT_IP'])){ $ip = $_SERVER['HTTP_CLIENT_IP']; }elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];else{ $ip = $_SERVER['REMOTE_ADDR']; } 返回 $ip;}
1赞 johnson23 2/12/2021
应使用抽象 IP 检测。价值在于,它会让你知道IP是在代理还是VPN后面,我认为这很重要。他们有一个PHP片段,你可以从中复制你的请求。

答:

106赞 Kyle Cronin 9/11/2008 #1

它应该包含在变量中。$_SERVER['REMOTE_ADDR']

552赞 Tim Kennedy 9/11/2008 #2

$_SERVER['REMOTE_ADDR']实际上可能不包含真实的客户端 IP 地址,因为它会为您提供通过代理连接的客户端的代理地址。那可能 不过,要成为你真正想要的,这取决于你对IP做了什么。如果您试图查看您的流量来自哪里,或者记住用户上次连接的 IP,代理或 NAT 网关的公共 IP 可能更适合存储,那么某人的私人RFC1918地址可能对您没有任何好处。

有几个 HTTP 标头,例如,它们可能由各种代理设置,也可能不由各种代理设置。问题在于,这些只是任何人都可以设置的HTTP标头。无法保证其内容。 是 Web 服务器从中接收连接并将响应发送到的实际物理 IP 地址。其他任何事情都只是任意和自愿的信息。只有一种情况可以信任此信息:您正在控制设置此标头的代理。这意味着只有当您 100% 知道标题的设置位置和方式时,您才应该注意任何重要的事情。X-Forwarded-For$_SERVER['REMOTE_ADDR']

话虽如此,下面是一些示例代码:

if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
    $ip = $_SERVER['REMOTE_ADDR'];
}

编者按:使用上述代码具有安全隐患。客户端可以将所有 HTTP 标头信息(即 )设置为它想要的任何任意值。因此,使用起来要可靠得多,因为用户无法设置。$_SERVER['HTTP_...$_SERVER['REMOTE_ADDR']

寄件人:http://roshanbh.com.np/2007/12/getting-real-ip-address-in-php.html

评论

242赞 Janos Pasztor 6/25/2013
除非您确切知道它的作用,否则不要使用上述代码!因此,我看到了巨大的安全漏洞。客户端可以将标头设置为所需的任意值。除非您有受信任的反向代理,否则不应使用这些值中的任何一个。X-Forwarded-ForClient-IP
30赞 lostphilosopher 1/28/2014
关于 Janoszen 的评论,一种选择是 PHP 的 filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)。
9赞 Martin Tournoij 2/13/2014
X-Forwarded-For可能包含多个 IP 地址,用逗号分隔;并且应该真正被“解析”而不是从表面上看(AFAIK,它几乎从不包含单个 IP)。
3赞 Tim Seguine 4/16/2014
@lostphilosopher这是一件合理的事情,它会使它更可靠,但不幸的是,它仍然允许欺骗。
8赞 Jon Watte 12/1/2014
对于缩放大小的站点,Web 应用程序服务器前面将有负载平衡器和/或反向代理。您必须配置这些负载平衡器或代理,以删除任何外部 X-Forwarded-For 标头,而是插入它们自己的 IP 地址,以便为连接客户端看到的 IP 地址。
238赞 lemon 6/9/2010 #3
echo $_SERVER['REMOTE_ADDR'];

http://php.net/manual/en/reserved.variables.server.php

评论

2赞 Anup Prakash 6/9/2010
实际上,我想知道使用我网站的客户的IP地址。不是我的页面上传或执行的服务器 IP 地址。请帮帮我。
53赞 Artefacto 6/9/2010
@Anup Prakash 就是这样——因此是“REMOTE”(从剧本的角度来看)。
92赞 Ussama Dahnin 10/8/2013
因为你在 localhost ;)
97赞 Camilo Martin 12/30/2013
@SiKni8 是 IPv6 的等价物::1127.0.0.1
17赞 Kristian 10/17/2015
@CamiloMartin你刚刚教会了我一些东西。凉!
30赞 kainosnous 6/9/2010 #4

答案是使用 $_SERVER 变量。例如,将返回客户端的 IP 地址。$_SERVER["REMOTE_ADDR"]

1541赞 Emil Vikström 6/9/2010 #5

无论您做什么,请确保不要信任从客户端发送的数据。 包含连接方的真实 IP 地址。这是你能找到的最可靠的值。$_SERVER['REMOTE_ADDR']

但是,它们可能位于代理服务器后面,在这种情况下,代理可能已将 ,但此值很容易被欺骗。例如,它可以由没有代理的人设置,或者 IP 可以是代理后面 LAN 的内部 IP。$_SERVER['HTTP_X_FORWARDED_FOR']

这意味着,如果您要保存 ,请确保保存该值。例如,通过将这两个值保存在数据库的不同字段中。$_SERVER['HTTP_X_FORWARDED_FOR']$_SERVER['REMOTE_ADDR']

如果要将 IP 作为字符串保存到数据库,请确保至少有 45 个字符的空间。IPv6 将继续存在,这些地址比旧的 IPv4 地址大。

(请注意,IPv6 通常最多使用 39 个字符,但 IPv4 地址也有一个特殊的 IPv6 表示法,其完整形式最多可以是 45 个字符。因此,如果您知道自己在做什么,则可以使用 39 个字符,但如果您只想设置并忘记它,请使用 45 个字符)。

评论

4赞 Blue 12/23/2014
不错的答案!我已经为我的服务器使用了 $_SERVER['REMOTE_ADDR'],我喜欢您包含另一种方式,以及优点和缺点。
61赞 Pacerier 6/29/2015
注意:可能包含 TCP 连接的真实 IP。这完全取决于您的 SAPI。确保正确配置了 SAPI,以便实际返回 TCP 连接的 IP。如果做不到这一点,可能会导致一些严重的漏洞,例如,StackExchange 过去常常通过检查它是否与“localhost”匹配来授予管理员访问权限,不幸的是 SAPI 的配置................................................................................................................REMOTE_ADDR$_SERVER['REMOTE_ADDR']REMOTE_ADDR
49赞 Pacerier 6/29/2015
..........................................................................存在一个漏洞(它作为输入),该漏洞允许非管理员通过更改标头来获得管理员访问权限。另请参阅 blog.ircmaxell.com/2012/11/anatomy-of-attack-how-i-hacked.htmlHTTP_X_FORWARDED_FORHTTP_X_FORWARDED_FOR
0赞 Aquaholic 11/21/2021
@EmilVikström 我尝试回显一切 - $_SERVER[“HTTP_CLIENT_IP”];$_SERVER['REMOTE_ADDR'];$_SERVER['HTTP_X_FORWARDED_FOR'];$_SERVER['HTTP_X_FORWARDED'];$_SERVER['HTTP_FORWARDED_FOR'];$_SERVER['HTTP_FORWARDED'];和 $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];- 唯一一个从 BOTH 返回一些 IP 值 - 通过我的浏览器和代理服务器直接访问的是 REMOTE_ADDR var。其余所有 6 个变量都以 BLANKS 的形式出现。这里错过了什么?.....
0赞 Aquaholic 11/21/2021
.....@Pacerier - 您方面对上述内容的任何评论
11赞 Johan Wikström 5/10/2011 #6

我喜欢这个代码片段:

function getClientIP() {

    if (isset($_SERVER)) {

        if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
            return $_SERVER["HTTP_X_FORWARDED_FOR"];

        if (isset($_SERVER["HTTP_CLIENT_IP"]))
            return $_SERVER["HTTP_CLIENT_IP"];

        return $_SERVER["REMOTE_ADDR"];
    }

    if (getenv('HTTP_X_FORWARDED_FOR'))
        return getenv('HTTP_X_FORWARDED_FOR');

    if (getenv('HTTP_CLIENT_IP'))
        return getenv('HTTP_CLIENT_IP');

    return getenv('REMOTE_ADDR');
}

评论

3赞 Pacerier 10/2/2011
我的意思是有什么意义..不会给你同样的东西吗?getenv$_SERVER
0赞 Mavelo 1/9/2015
@Paceriermy猜测是旧版本的 PHP,其中 _SERVER 美元尚未上市;)
0赞 nu everest 12/11/2016
@Johan 为什么不直接返回一个包含这三个的数组呢?
0赞 StackOverflowed 12/18/2016
@nueverest 因为用户通常不会从 3 个不同的 IP 访问您的网站。你想返回适用于他的那个。
10赞 carbontwelve 2/13/2014 #7

这是我使用的方法,它验证 IPv4 输入:

// Get user IP address
if ( isset($_SERVER['HTTP_CLIENT_IP']) && ! empty($_SERVER['HTTP_CLIENT_IP'])) {
    $ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif ( isset($_SERVER['HTTP_X_FORWARDED_FOR']) && ! empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
    $ip = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
}

$ip = filter_var($ip, FILTER_VALIDATE_IP);
$ip = ($ip === false) ? '0.0.0.0' : $ip;

评论

25赞 deceze 4/27/2014
感谢您允许我通过简单地设置HTTP标头来欺骗我的IP地址!
64赞 algorhythm 7/7/2014 #8

我最喜欢的解决方案是 Zend Framework 2 的使用方式。它还考虑了属性 ,但它声明了一个类来设置一些受信任的代理,并返回一个 IP 地址而不是数组。我认为这是最接近它的解决方案:$_SERVERHTTP_X_FORWARDED_FORHTTP_CLIENT_IPREMOTE_ADDR

class RemoteAddress
{
    /**
     * Whether to use proxy addresses or not.
     *
     * As default this setting is disabled - IP address is mostly needed to increase
     * security. HTTP_* are not reliable since can easily be spoofed. It can be enabled
     * just for more flexibility, but if user uses proxy to connect to trusted services
     * it's his/her own risk, only reliable field for IP address is $_SERVER['REMOTE_ADDR'].
     *
     * @var bool
     */
    protected $useProxy = false;

    /**
     * List of trusted proxy IP addresses
     *
     * @var array
     */
    protected $trustedProxies = array();

    /**
     * HTTP header to introspect for proxies
     *
     * @var string
     */
    protected $proxyHeader = 'HTTP_X_FORWARDED_FOR';

    // [...]

    /**
     * Returns client IP address.
     *
     * @return string IP address.
     */
    public function getIpAddress()
    {
        $ip = $this->getIpAddressFromProxy();
        if ($ip) {
            return $ip;
        }

        // direct IP address
        if (isset($_SERVER['REMOTE_ADDR'])) {
            return $_SERVER['REMOTE_ADDR'];
        }

        return '';
    }

    /**
     * Attempt to get the IP address for a proxied client
     *
     * @see http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#section-5.2
     * @return false|string
     */
    protected function getIpAddressFromProxy()
    {
        if (!$this->useProxy
            || (isset($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], $this->trustedProxies))
        ) {
            return false;
        }

        $header = $this->proxyHeader;
        if (!isset($_SERVER[$header]) || empty($_SERVER[$header])) {
            return false;
        }

        // Extract IPs
        $ips = explode(',', $_SERVER[$header]);
        // trim, so we can compare against trusted proxies properly
        $ips = array_map('trim', $ips);
        // remove trusted proxy IPs
        $ips = array_diff($ips, $this->trustedProxies);

        // Any left?
        if (empty($ips)) {
            return false;
        }

        // Since we've removed any known, trusted proxy servers, the right-most
        // address represents the first IP we do not know about -- i.e., we do
        // not know if it is a proxy server, or a client. As such, we treat it
        // as the originating IP.
        // @see http://en.wikipedia.org/wiki/X-Forwarded-For
        $ip = array_pop($ips);
        return $ip;
    }

    // [...]
}

在此处查看完整代码: https://raw.githubusercontent.com/zendframework/zend-http/master/src/PhpEnvironment/RemoteAddress.php

评论

3赞 jnhghy - Alexandru Jantea 1/19/2015
很好的答案!使用经过生产测试的代码,在这样一个大的框架中开发和使用是您可以做的最好的事情之一:)
3赞 M H 7/19/2015
所以等等 zend 不过滤任何东西?我应该看到类似的东西: filter_var( $_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 );
0赞 Marius.C 8/10/2015
@Hanoncs你为什么要这样做?欺骗远程地址非常困难
1赞 algorhythm 8/11/2015
@Hanoncs我认为您必须在使用此类获取值后检查该值。它不是它逻辑的一部分。它只是从变量中获取值,并跳过一些已定义的和众所周知的代理服务器。就这样。如果您认为返回值不安全,请检查它或向 PHP 开发人员报告错误。$_SERVER
159赞 josh123a123 10/9/2014 #9

下面是一个更简洁的代码示例,介绍了获取用户 IP 地址的好方法。

$ip = $_SERVER['HTTP_CLIENT_IP'] 
   ? $_SERVER['HTTP_CLIENT_IP'] 
   : ($_SERVER['HTTP_X_FORWARDED_FOR'] 
        ? $_SERVER['HTTP_X_FORWARDED_FOR'] 
        : $_SERVER['REMOTE_ADDR']);

下面是一个使用 elvis 运算符的较短版本:

$_SERVER['HTTP_CLIENT_IP'] 
   ? : ($_SERVER['HTTP_X_FORWARDED_FOR'] 
   ? : $_SERVER['REMOTE_ADDR']);

这是一个使用 isset 删除通知的版本(谢谢你,@shasi kanth):

$ip = isset($_SERVER['HTTP_CLIENT_IP']) 
    ? $_SERVER['HTTP_CLIENT_IP'] 
    : (isset($_SERVER['HTTP_X_FORWARDED_FOR']) 
      ? $_SERVER['HTTP_X_FORWARDED_FOR'] 
      : $_SERVER['REMOTE_ADDR']);

评论

13赞 josh123a123 11/25/2014
永远记住清理任何可能被用户修改的输入。这是其中之一。
8赞 DavidTaubmann 6/24/2016
我相信代码缺少一些表达式,优先级顺序颠倒了,所以它应该是这样的: 尽管如此,非常好的一个。$ip = $_SERVER['HTTP_CLIENT_IP']?$_SERVER['HTTP_CLIENT_IP']:($_SERVER['HTTP_X_FORWARDED_FOR']?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR']);
2赞 josh123a123 6/28/2016
好渔获。我已经调整了帖子。 谢谢!
8赞 shasi kanth 11/29/2017
刚刚添加了 isset() 来删除通知: $ip = isset($_SERVER['HTTP_CLIENT_IP'])?$_SERVER['HTTP_CLIENT_IP']:isset($_SERVER['HTTP_X_FORWARDE D_FOR'])?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR'];
3赞 Frank 2/19/2018
正如有人指出的那样,所有三个示例都充满了隐藏字符(U+200C 和 U+200B)。不要将它们粘贴到您的 php 脚本中,否则您将收到 PARSE ERRORS。如果要查看所有隐藏的字符,请将这些行粘贴到此处: soscisurvey.de/tools/view-chars.php 。请清理该代码!
-1赞 B.F. 12/6/2014 #10

像下面这样吗?

if (($ip=filter_input(INPUT_SERVER, 'REMOTE_ADDR', validate_ip)) === false or empty($ip)) {
    exit;
}
echo $ip;

聚苯乙烯

if (($ip=filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP|FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE)) === false) {
    header('HTTP/1.0 400 Bad Request');
    exit;
}

所有以“HTTP_”或“X-”开头的标头都可能是欺骗性的,分别是用户定义的。如果您想跟踪,请使用 cookie 等。

39赞 manuelbcd 1/29/2015 #11

以下是我发现的最先进的方法,我过去已经尝试过其他一些方法。确保获取访问者的 IP 地址是有效的(但请注意,任何黑客都可以轻松伪造 IP 地址)。

function get_ip_address() {

    // Check for shared Internet/ISP IP
    if (!empty($_SERVER['HTTP_CLIENT_IP']) && validate_ip($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    }

    // Check for IP addresses passing through proxies
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {

        // Check if multiple IP addresses exist in var
        if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false) {
            $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            foreach ($iplist as $ip) {
                if (validate_ip($ip))
                    return $ip;
            }
        }
        else {
            if (validate_ip($_SERVER['HTTP_X_FORWARDED_FOR']))
                return $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
    }
    if (!empty($_SERVER['HTTP_X_FORWARDED']) && validate_ip($_SERVER['HTTP_X_FORWARDED']))
        return $_SERVER['HTTP_X_FORWARDED'];
    if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && validate_ip($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
        return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
    if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && validate_ip($_SERVER['HTTP_FORWARDED_FOR']))
        return $_SERVER['HTTP_FORWARDED_FOR'];
    if (!empty($_SERVER['HTTP_FORWARDED']) && validate_ip($_SERVER['HTTP_FORWARDED']))
        return $_SERVER['HTTP_FORWARDED'];

    // Return unreliable IP address since all else failed
    return $_SERVER['REMOTE_ADDR'];
}

/**
 * Ensures an IP address is both a valid IP address and does not fall within
 * a private network range.
 */
function validate_ip($ip) {

    if (strtolower($ip) === 'unknown')
        return false;

    // Generate IPv4 network address
    $ip = ip2long($ip);

    // If the IP address is set and not equivalent to 255.255.255.255
    if ($ip !== false && $ip !== -1) {
        // Make sure to get unsigned long representation of IP address
        // due to discrepancies between 32 and 64 bit OSes and
        // signed numbers (ints default to signed in PHP)
        $ip = sprintf('%u', $ip);

        // Do private network range checking
        if ($ip >= 0 && $ip <= 50331647)
            return false;
        if ($ip >= 167772160 && $ip <= 184549375)
            return false;
        if ($ip >= 2130706432 && $ip <= 2147483647)
            return false;
        if ($ip >= 2851995648 && $ip <= 2852061183)
            return false;
        if ($ip >= 2886729728 && $ip <= 2887778303)
            return false;
        if ($ip >= 3221225984 && $ip <= 3221226239)
            return false;
        if ($ip >= 3232235520 && $ip <= 3232301055)
            return false;
        if ($ip >= 4294967040)
            return false;
    }
    return true;
}

评论

10赞 tobltobs 1/29/2016
这是错误的。HTTP_CLIENT_IP比REMOTE_ADDR更不可靠,IP验证功能是无稽之谈。
0赞 Mike Q 8/31/2017
@tobltobs .你这么说很有趣,但这是唯一一个在重定向页面加载时在清漆框后面真正为我工作的功能集。我竖起大拇指.
0赞 Peter Mortensen 3/3/2019
链接(实际上)已断开。
0赞 manuelbcd 4/29/2019
链接删除了,页面似乎消失了。谢谢
0赞 FabianoLothor 5/4/2019
这是2019年获得ip的最佳方式吗?
4赞 GYANENDRA PRASAD PANIGRAHI 5/26/2015 #12

试试这个:

 $_SERVER['REMOTE_ADDR'];

评论

33赞 kenorb 5/26/2015
这已经提到过几次了,你的回答实际上并没有增加任何有用的东西。
0赞 Chris 9/10/2015
“你的回答实际上没有任何用处”——不知道你的意思,它回答了被问到的问题。这怎么没有用?
11赞 Mg Thar 10/12/2015
因为他正在回答 5 年前的问题,并且已经回答了很多相同且更好的答案。
11赞 Mahfuz Ahmed 12/28/2015 #13
$ip = "";

if (!empty($_SERVER["HTTP_CLIENT_IP"]))
{
    // Check for IP address from shared Internet
    $ip = $_SERVER["HTTP_CLIENT_IP"];
}
elseif (!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
{
    // Check for the proxy user
    $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
}
else
{
    $ip = $_SERVER["REMOTE_ADDR"];
}
echo $ip;

评论

0赞 Robin Kanters 1/8/2016
第一个代码段如何返回客户端的 IP 地址?在我看来,它会回显服务器的地址。
0赞 Mahfuz Ahmed 1/10/2016
谢谢罗宾。是的,有时你不会得到正确的结果。请使用第二种解决方案。
0赞 Daksh B 1/18/2016
@MahfuzAhmed,你能说出来,file_get_contents()是做什么的吗?以及如何通过 file_get_contents() 获取 IP
0赞 Maciej Paprocki 1/18/2016
file_get_contents在这里完全没用:)
1赞 Yash Kumar Verma 4/8/2016
在第 1 行中初始化的需要是什么。如果所有条件都失败,那么也会运行。$ip$ip = $_SERVER['REMOTE_ADDR']
10赞 Yash Kumar Verma 2/24/2016 #14

好吧,这可以通过使用名为 的变量来简单地完成。GLOBAL$_SERVER

是一个数组,其属性名称为 。$_SERVERREMOTE_ADDR

只需像这样分配它:

$userIp = $_SERVER['REMOTE_ADDR'];

或者直接像或一样使用它。echo $_SERVER['REMOTE_ADDR'];echo ($_SERVER['REMOTE_ADDR']);

6赞 Akam 4/27/2016 #15

以下函数确定所有可能性,并以逗号分隔的格式(ip、ip 等)返回值。

它还具有可选的验证功能(默认禁用的第一个参数),用于根据(专用范围和保留范围)验证 IP 地址。

<?php
echo GetClientIP(true);

function GetClientIP($validate = False) {
  $ipkeys = array(
  'REMOTE_ADDR',
  'HTTP_CLIENT_IP',
  'HTTP_X_FORWARDED_FOR',
  'HTTP_X_FORWARDED',
  'HTTP_FORWARDED_FOR',
  'HTTP_FORWARDED',
  'HTTP_X_CLUSTER_CLIENT_IP'
  );

  /*
  Now we check each key against $_SERVER if containing such value
  */
  $ip = array();
  foreach ($ipkeys as $keyword) {
    if (isset($_SERVER[$keyword])) {
      if ($validate) {
        if (ValidatePublicIP($_SERVER[$keyword])) {
          $ip[] = $_SERVER[$keyword];
        }
      }
      else{
        $ip[] = $_SERVER[$keyword];
      }
    }
  }

  $ip = ( empty($ip) ? 'Unknown' : implode(", ", $ip) );
  return $ip;
}

function ValidatePublicIP($ip){
  if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
    return true;
  }
  else {
    return false;
  }
}
12赞 Siamak 5/26/2016 #16

正如之前所有其他人所说,您可以使用来获取客户端 IP 地址。$_SERVER['REMOTE_ADDR'];

此外,如果您需要有关用户的更多信息,可以使用以下命令:

<?php
    $ip = '0.0.0.0';
    $ip = $_SERVER['REMOTE_ADDR'];
    $clientDetails = json_decode(file_get_contents("http://ipinfo.io/$ip/json"));
    echo "You're logged in from: <b>" . $clientDetails->country . "</b>";
?>

客户更具体的信息会进入$clientDetails。
您可以通过以下方式获取存储在变量中的 JSON 项目$clientDetails:$clientDetails->PostalCode/hostname/region/loc...

我正在使用 ipinfo.io 来获取额外的信息。

7赞 Ivan Barayev 6/19/2016 #17

此功能很紧凑,您可以在任何地方使用它。但!

别忘了这一点!在这种类型的函数或代码块中,不能保证记录用户的真实 IP 地址,因为某些用户可以使用代理或其他安全网关来不可见或无法跟踪

PHP函数:

function GetIP()
{
    if ( getenv("HTTP_CLIENT_IP") ) {
        $ip = getenv("HTTP_CLIENT_IP");
    } elseif ( getenv("HTTP_X_FORWARDED_FOR") ) {
        $ip = getenv("HTTP_X_FORWARDED_FOR");
        if ( strstr($ip, ',') ) {
            $tmp = explode(',', $ip);
            $ip = trim($tmp[0]);
        }
    } else {
        $ip = getenv("REMOTE_ADDR");
    }
    return $ip;
}

用法:

$IP = GetIP();或直接GetIP();

1赞 oriadam 11/17/2016 #18

这是一个简单的衬里

$ip = $_SERVER['HTTP_X_FORWARDED_FOR']?: $_SERVER['HTTP_CLIENT_IP']?: $_SERVER['REMOTE_ADDR'];

编辑:

上面的代码可能会返回保留地址(如 10.0.0.1)、途中所有代理服务器的地址列表等。 若要处理这些情况,请使用以下代码:

function valid_ip($ip) {
    // for list of reserved IP addresses, see https://en.wikipedia.org/wiki/Reserved_IP_addresses
    return $ip && substr($ip, 0, 4) != '127.' && substr($ip, 0, 4) != '127.' && substr($ip, 0, 3) != '10.' && substr($ip, 0, 2) != '0.' ? $ip : false;
}

function get_client_ip() {
    // using explode to get only client ip from list of forwarders. see https://en.wikipedia.org/wiki/X-Forwarded-For
    return
    @$_SERVER['HTTP_X_FORWARDED_FOR'] ? explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'], 2)[0] :
    @$_SERVER['HTTP_CLIENT_IP'] ? explode(',', $_SERVER['HTTP_CLIENT_IP'], 2)[0] :
    valid_ip(@$_SERVER['REMOTE_ADDR']) ?:
    'UNKNOWN';
}

echo get_client_ip();
56赞 K.Suthagar 12/29/2016 #19

互联网背后有不同类型的用户,因此我们希望从不同的部分捕获 IP 地址。这些是:

1. $_SERVER['REMOTE_ADDR'] - 这包含客户端的真实 IP 地址。这是您可以从用户那里找到的最可靠的价值。

2. $_SERVER['REMOTE_HOST'] - 这将获取用户从中查看当前页面的主机名。但是要使此脚本正常工作,必须在 httpd.conf 内部配置主机名查找。

3. $_SERVER['HTTP_CLIENT_IP'] - 当用户来自共享 Internet 服务时,这将获取 IP 地址。

4. $_SERVER['HTTP_X_FORWARDED_FOR'] - 当用户在代理后面时,这将从他/她那里获取 IP 地址。

所以我们可以使用下面的组合函数,从不同位置观看的用户那里获取真实的IP地址,

// Function to get the user IP address
function getUserIP() {
    $ipaddress = '';
    if (isset($_SERVER['HTTP_CLIENT_IP']))
        $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
    else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_X_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
    else if(isset($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
        $ipaddress = $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
    else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_FORWARDED'];
    else if(isset($_SERVER['REMOTE_ADDR']))
        $ipaddress = $_SERVER['REMOTE_ADDR'];
    else
        $ipaddress = 'UNKNOWN';
    return $ipaddress;
}

评论

11赞 8/10/2017
真的很容易伪造。我刚刚尝试在我自己的网站上使用 Requestly Chrome 扩展程序将 Client-ip 标头设置为“111.111.111.111”。
6赞 Alexander Yancharuk 10/5/2018 #20

用于获取 IP 地址的安全且可感知警告的代码段:

$ip = filter_input(INPUT_SERVER, 'HTTP_CLIENT_IP', FILTER_VALIDATE_IP)
    ?: filter_input(INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_VALIDATE_IP)
    ?: $_SERVER['REMOTE_ADDR']
    ?? '0.0.0.0'; // Or other value fits "not defined" in your logic
1赞 Mohamad Hamouday 3/1/2019 #21

此函数应按预期工作

function Get_User_Ip()
{
    $IP = false;
    if (getenv('HTTP_CLIENT_IP'))
    {
        $IP = getenv('HTTP_CLIENT_IP');
    }
    else if(getenv('HTTP_X_FORWARDED_FOR'))
    {
        $IP = getenv('HTTP_X_FORWARDED_FOR');
    }
    else if(getenv('HTTP_X_FORWARDED'))
    {
        $IP = getenv('HTTP_X_FORWARDED');
    }
    else if(getenv('HTTP_FORWARDED_FOR'))
    {
        $IP = getenv('HTTP_FORWARDED_FOR');
    }
    else if(getenv('HTTP_FORWARDED'))
    {
        $IP = getenv('HTTP_FORWARDED');
    }
    else if(getenv('REMOTE_ADDR'))
    {
        $IP = getenv('REMOTE_ADDR');
    }

    //If HTTP_X_FORWARDED_FOR == server ip
    if((($IP) && ($IP == getenv('SERVER_ADDR')) && (getenv('REMOTE_ADDR')) || (!filter_var($IP, FILTER_VALIDATE_IP))))
    {
        $IP = getenv('REMOTE_ADDR');
    }

    if($IP)
    {
        if(!filter_var($IP, FILTER_VALIDATE_IP))
        {
            $IP = false;
        }
    }
    else
    {
        $IP = false;
    }
    return $IP;
}
5赞 user8031209 4/1/2019 #22

<?php
/**
 * Function to get the client ip address
 *
 * @return string The Ip address
 */
function getIp(): string {
    if (! empty($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    }

    if (! empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        return $_SERVER['HTTP_X_FORWARDED_FOR'];
    }

    return $_SERVER['REMOTE_ADDR'] ?? '?';
}

甚至更小

/**
 * Function to get the client ip address
 *
 * @return string The Ip address
 */
function getIp(): string {
    return $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '';
}
3赞 vr_driver 4/29/2019 #23

就这一点,我很惊讶它还没有被提及,是获取那些位于CloudFlare基础设施后面的站点的正确IP地址。它将破坏您的 IP 地址,并赋予它们相同的值。 幸运的是,他们也有一些可用的服务器标头。 与其我重写已经写过的东西,不如看看这里以获得更简洁的答案,是的,我很久以前也经历过这个过程。https://stackoverflow.com/a/14985633/1190051

14赞 Keaser 5/20/2019 #24
function get_client_ip()
{
    foreach (array(
                'HTTP_CLIENT_IP',
                'HTTP_X_FORWARDED_FOR',
                'HTTP_X_FORWARDED',
                'HTTP_X_CLUSTER_CLIENT_IP',
                'HTTP_FORWARDED_FOR',
                'HTTP_FORWARDED',
                'REMOTE_ADDR') as $key) {
        if (array_key_exists($key, $_SERVER)) {
            foreach (explode(',', $_SERVER[$key]) as $ip) {
                $ip = trim($ip);
                if ((bool) filter_var($ip, FILTER_VALIDATE_IP,
                                FILTER_FLAG_IPV4 |
                                FILTER_FLAG_NO_PRIV_RANGE |
                                FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
    }
    return null;
}

或者压缩版本:

function get_ip() {
    foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key) {
        if (array_key_exists($key, $_SERVER) === true) {
            foreach (array_map('trim', explode(',', $_SERVER[$key])) as $ip) {
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
                    return $ip;
                }
            }
        }
    }
}

15赞 ws-ono 6/2/2019 #25

这里有一些代码,应该通过检查各种来源来选择有效的 IP。

首先,它会检查“REMOTE_ADDR”是否是公共 IP(而不是您信任的反向代理之一),然后遍历其中一个 HTTP 标头,直到找到公共 IP 并返回它。(菲律宾比索 5.2+)

只要反向代理受信任或服务器直接与客户端连接,它就应该是可靠的。

//Get client's IP or null if nothing looks valid
function ip_get($allow_private = false)
{
  //Place your trusted proxy server IPs here.
  $proxy_ip = ['127.0.0.1'];

  //The header to look for (Make sure to pick the one that your trusted reverse proxy is sending or else you can get spoofed)
  $header = 'HTTP_X_FORWARDED_FOR'; //HTTP_CLIENT_IP, HTTP_X_FORWARDED, HTTP_FORWARDED_FOR, HTTP_FORWARDED

  //If 'REMOTE_ADDR' seems to be a valid client IP, use it.
  if(ip_check($_SERVER['REMOTE_ADDR'], $allow_private, $proxy_ip)) return $_SERVER['REMOTE_ADDR'];

  if(isset($_SERVER[$header]))
  {
    //Split comma separated values [1] in the header and traverse the proxy chain backwards.
    //[1] https://en.wikipedia.org/wiki/X-Forwarded-For#Format
    $chain = array_reverse(preg_split('/\s*,\s*/', $_SERVER[$header]));
    foreach($chain as $ip) if(ip_check($ip, $allow_private, $proxy_ip)) return $ip;
  }

   return null;
}

//Check for valid IP. If 'allow_private' flag is set to truthy, it allows private IP ranges as valid client IP as well. (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
//Pass your trusted reverse proxy IPs as $proxy_ip to exclude them from being valid.
function ip_check($ip, $allow_private = false, $proxy_ip = [])
{
  if(!is_string($ip) || is_array($proxy_ip) && in_array($ip, $proxy_ip)) return false;
  $filter_flag = FILTER_FLAG_NO_RES_RANGE;

  if(!$allow_private)
  {
    //Disallow loopback IP range which doesn't get filtered via 'FILTER_FLAG_NO_PRIV_RANGE' [1]
    //[1] https://www.php.net/manual/en/filter.filters.validate.php
    if(preg_match('/^127\.$/', $ip)) return false;
    $filter_flag |= FILTER_FLAG_NO_PRIV_RANGE;
  }

  return filter_var($ip, FILTER_VALIDATE_IP, $filter_flag) !== false;
}
10赞 SIE 6/11/2019 #26

其中之一:

    $ip = $_SERVER['REMOTE_ADDR'];
    $ip = $_SERVER['HTTP_CLIENT_IP'];
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    $ip = $_SERVER['HTTP_X_FORWARDED'];
    $ip = $_SERVER['HTTP_FORWARDED_FOR'];
    $ip = $_SERVER['HTTP_FORWARDED'];

评论

0赞 Jsowa 5/10/2020
有关服务器变量的文档:php.net/manual/en/reserved.variables.server.php
26赞 Stergios Zg. 6/23/2019 #27

快速解决方案(无错误)

function getClientIP():string
{
    $keys=array('HTTP_CLIENT_IP','HTTP_X_FORWARDED_FOR','HTTP_X_FORWARDED','HTTP_FORWARDED_FOR','HTTP_FORWARDED','REMOTE_ADDR');
    foreach($keys as $k)
    {
        if (!empty($_SERVER[$k]) && filter_var($_SERVER[$k], FILTER_VALIDATE_IP))
        {
            return $_SERVER[$k];
        }
    }
    return "UNKNOWN";
}

评论

5赞 hanshenrik 11/2/2019
警告,黑客可以通过发送虚假的 HTTP 标头轻松欺骗 IPX-FORWARDED-FOR: fakeip
0赞 Stergios Zg. 12/14/2020
当然,但如果你使用 NGINX,clientIP 通常在 “HTTP_X_FORWARDED_FOR” 上
4赞 Jonathan Rosa 1/19/2021
这可能无法按预期工作:1.此处的所有标头都允许使用逗号和/或分号,因此您必须标记字符串(即,);2. 不存在;3. here 的用法(标准化)将始终无法通过filter_var测试,因为它使用自己的语法(即,)。strtok($k, ';,')HTTP_X_FORWARDEDHTTP_FORWARDEDfor=1.1.1.1;by=1.1.1.0
-3赞 HRM 7/22/2021 #28

要获取客户端 IP 地址,请使用 。getenv("REMOTE_ADDR")

例如

$ip_address = getenv("REMOTE_ADDR");
echo $ip_address;

如果您使用 localhost 调用您的服务器,它将打印出 . 因此,请使用直接服务器 IP 地址或域呼叫您的服务器。::1

2赞 jerryurenaa 12/4/2021 #29

在PHP中,获取公共IP的最后一个选项应该总是出于太多的安全原因。$_SERVER["REMOTE_ADDR"]

以下是获取客户端的已验证 IP 地址的解决方法。

public static function getPublicIP() : string
    {
        $realIP = "Invalid IP Address";

        $activeHeaders = [];

        $headers = [
            "HTTP_CLIENT_IP",
            "HTTP_PRAGMA",
            "HTTP_XONNECTION",
            "HTTP_CACHE_INFO",
            "HTTP_XPROXY",
            "HTTP_PROXY",
            "HTTP_PROXY_CONNECTION",
            "HTTP_VIA",
            "HTTP_X_COMING_FROM",
            "HTTP_COMING_FROM",
            "HTTP_X_FORWARDED_FOR",
            "HTTP_X_FORWARDED",
            "HTTP_X_CLUSTER_CLIENT_IP",
            "HTTP_FORWARDED_FOR",
            "HTTP_FORWARDED",
            "ZHTTP_CACHE_CONTROL",
            "REMOTE_ADDR" #this should be the last option
        ];

        #Find active headers
        foreach ($headers as $key)
        {
            if (array_key_exists($key, $_SERVER))
            {
                $activeHeaders[$key] = $_SERVER[$key];
            }
        }

         #Reemove remote address since we got more options to choose from
        if(count($activeHeaders) > 1)
        {
            unset($activeHeaders["REMOTE_ADDR"]);
        }

        #Pick a random item now that we have a secure way.
        $realIP = $activeHeaders[array_rand($activeHeaders)];

        #Validate the public IP
        if (filter_var($realIP, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
        {
            return $realIP;
        }

        return $realIP;
    }

正如你在这里看到的,$_SERVER[“REMOTE_ADDR”] 是我们对 IP 的最后一个选择。收到 IP 后,我们还会验证 IP,以确保质量和安全性。

3赞 Faraz Ahmad 6/29/2022 #30

如果您不喜欢使用 if-else/switch 语句,那么以下解决方案适合您。

function get_client_ip()
{
    $fields = array(
        'HTTP_CF_CONNECTING_IP',
        'HTTP_X_SUCURI_CLIENTIP',
        'HTTP_CLIENT_IP',
        'HTTP_X_FORWARDED_FOR',
        'HTTP_X_FORWARDED',
        'HTTP_FORWARDED_FOR',
        'HTTP_FORWARDED',
        'REMOTE_ADDR',
        // more custom fields
    );

    foreach ($fields as $ip_field) {
        if (!empty($_SERVER[$ip_field])) {
            return $_SERVER[$ip_field];
        }
    }

    return null;
}
1赞 Shriyank 7/5/2022 #31

您可以使用以下功能

function get_client_ip() {
    $ipaddress = '';
    if (isset($_SERVER['HTTP_CLIENT_IP']))
        $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
    else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_X_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
    else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_FORWARDED'];
    else if(isset($_SERVER['REMOTE_ADDR']))
        $ipaddress = $_SERVER['REMOTE_ADDR'];
    else
        $ipaddress = 'UNKNOWN';
    return $ipaddress;
}
3赞 zac 10/15/2022 #32

我使用了其他答案之一,并添加了一些其他内容,例如CloudFlare代理和NGINX代理检测。

/**
 * Gets, validates and returns the connecting client's IP
 */
function getClientIP(){

    // Get real visitor IP behind CloudFlare network
    if (!empty($_SERVER["HTTP_CF_CONNECTING_IP"]) && validateIP($_SERVER['HTTP_CF_CONNECTING_IP'])) {
        return $_SERVER["HTTP_CF_CONNECTING_IP"];
    }

    // Get real visitor IP behind NGINX proxy - https://easyengine.io/tutorials/nginx/forwarding-visitors-real-ip/
    if (!empty($_SERVER["HTTP_X_REAL_IP"]) && validateIP($_SERVER['HTTP_X_REAL_IP'])) {
        return $_SERVER["HTTP_X_REAL_IP"];
    }

    // Check for shared Internet/ISP IP
    if (!empty($_SERVER['HTTP_CLIENT_IP']) && validateIP($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    }

    // Check for IP addresses passing through proxies
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {

        // Check if multiple IP addresses exist in var
        if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false) {
            $iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            foreach ($iplist as $ip) {
                if (validateIP($ip))
                    return $ip;
            }
        }
        else {
            if (validateIP($_SERVER['HTTP_X_FORWARDED_FOR']))
                return $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
    }

    if (!empty($_SERVER['HTTP_X_FORWARDED']) && validateIP($_SERVER['HTTP_X_FORWARDED']))
        return $_SERVER['HTTP_X_FORWARDED'];

    if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && validateIP($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
        return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];

    if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && validateIP($_SERVER['HTTP_FORWARDED_FOR']))
        return $_SERVER['HTTP_FORWARDED_FOR'];

    if (!empty($_SERVER['HTTP_FORWARDED']) && validateIP($_SERVER['HTTP_FORWARDED']))
        return $_SERVER['HTTP_FORWARDED'];

    // Return unreliable IP address since all else failed
    return $_SERVER['REMOTE_ADDR'];
}

/**
 * Ensures an IP address is both a valid IP address and does not fall within
 * a private network range.
 */
function validateIP($ip) {

    if (strtolower($ip) === 'unknown')
        return false;

    // Generate IPv4 network address
    $ip = ip2long($ip);

    // Do additional filtering on IP
    if(!filter_var($ip, FILTER_VALIDATE_IP))
        return false;

    // If the IP address is set and not equivalent to 255.255.255.255
    if ($ip !== false && $ip !== -1) {

        // Make sure to get unsigned long representation of IP address
        // due to discrepancies between 32 and 64 bit OSes and
        // signed numbers (ints default to signed in PHP)
        $ip = sprintf('%u', $ip);

        // Do private network range checking
        if ($ip >= 0 && $ip <= 50331647)
            return false;
        if ($ip >= 167772160 && $ip <= 184549375)
            return false;
        if ($ip >= 2130706432 && $ip <= 2147483647)
            return false;
        if ($ip >= 2851995648 && $ip <= 2852061183)
            return false;
        if ($ip >= 2886729728 && $ip <= 2887778303)
            return false;
        if ($ip >= 3221225984 && $ip <= 3221226239)
            return false;
        if ($ip >= 3232235520 && $ip <= 3232301055)
            return false;
        if ($ip >= 4294967040)
            return false;
    }
    return true;
}

我在生产节点中使用它,效果很好。由于大部分代码都来自这里,我在 GNU @ https://github.com/d3vdigital/whatsmyip-node 下发布了一个节点

评论

0赞 sij_a 2/23/2023
这有什么安全隐患吗?在上述所有验证之后,我是否可以将值保存到数据库中?
0赞 Helper123 3/12/2023 #33

使用 $ip = $_SERVER[“REMOTE_ADDR”] 这会将 IP 保存在 IP 变量中。

1赞 Mamedul Islam 5/2/2023 #34

我们可以将其用于 localhost 和网站

function get_ip() {
    $ip = '';
    if (isset($_SERVER['HTTP_CLIENT_IP'])){
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }else if(isset($_SERVER['HTTP_X_FORWARDED'])){
        $ip = $_SERVER['HTTP_X_FORWARDED'];
    }else if(isset($_SERVER['HTTP_FORWARDED_FOR'])){
        $ip = $_SERVER['HTTP_FORWARDED_FOR'];
    }else if(isset($_SERVER['HTTP_FORWARDED'])){
        $ip = $_SERVER['HTTP_FORWARDED'];
    }else if(isset($_SERVER['REMOTE_ADDR'])){
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    if( empty($ip) || $ip == '0.0.0.0' || substr( $ip, 0, 2 ) == '::' ){
        $ip = file_get_contents('https://api.ipify.org/');
        $ip = ($ip===false?$ip:'');
    }
    return $ip;
}
2赞 Emil M 6/21/2023 #35
<?php
/**
 * Get the real IP address of the client
 * 
 * @param array $trusted_proxies list of IP addresses of reverse proxy servers that you trust
 * @return mixed client IP or null
 */
function get_client_ip($trusted_proxies=[]) {
    // In cli mode, there is no remote address
    if (empty($_SERVER['REMOTE_ADDR'])) {
        return null;
    }

    $client_ip = $_SERVER['REMOTE_ADDR'];
    // If the remote address is not a trusted proxy, we shouldn't trust
    // any headers that malicious clients may send
    if (!in_array($client_ip, $trusted_proxies)) {
        return $client_ip;
    }

    // The request is coming from a trusted proxy, so we can trust the 
    // "forwarded for" headers
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        return $_SERVER['HTTP_X_FORWARDED_FOR'];
    }

    if (isset($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    }

    // No forwarded client IP header provided; this might be some kind 
    // of health check request. Just return the trusted proxy IP.
    return $client_ip;
}

// Example usage

// if the app doesn't work behind proxy server:
$ip = get_client_ip();

// if you're behind reverse proxy, pass the IP address like this:
$ip = get_client_ip(trusted_proxies: ['10.10.10.10']);

// or like this, for older PHP versions:
$ip = get_client_ip(['10.10.10.10']);

0赞 HGStyle 6/29/2023 #36

正如许多人所说,寻找真人 IP 的“反代理”有点难以创建,因为您需要信任的代理的 IP 列表。Cvolton 是 Geometry Dash 玩家,它用 PHP 和 MySQL 重新创建了整个游戏服务器,并具有绕过 Cloudflare 和 7mPl“localhost bug”获取用户 IP 的功能。这是代码(因为原始代码被分成 2 个文件,所以这里是融合文件)(获取 IP 函数也在程序的末尾):

/*
 * ip_in_range.php - Function to determine if an IP is located in a
 *                   specific range as specified via several alternative
 *                   formats.
 *
 * Network ranges can be specified as:
 * 1. Wildcard format:     1.2.3.*
 * 2. CIDR format:         1.2.3/24  OR  1.2.3.4/255.255.255.0
 * 3. Start-End IP format: 1.2.3.0-1.2.3.255
 *
 * Return value BOOLEAN : ip_in_range($ip, $range);
 *
 * Copyright 2008: Paul Gregg <[email protected]>
 * 10 January 2008
 * Version: 1.2
 *
 * Source website: http://www.pgregg.com/projects/php/ip_in_range/
 * Version 1.2
 *
 * This software is Donationware - if you feel you have benefited from
 * the use of this tool then please consider a donation. The value of
 * which is entirely left up to your discretion.
 * http://www.pgregg.com/donate/
 *
 * Please do not remove this header, or source attibution from this file.
 */

/*
* Modified by James Greene <[email protected]> to include IPV6 support
* (original version only supported IPV4).
* 21 May 2012
*/

class ipInRange {


    // decbin32
    // In order to simplify working with IP addresses (in binary) and their
    // netmasks, it is easier to ensure that the binary strings are padded
    // with zeros out to 32 characters - IP addresses are 32 bit numbers
    public static function decbin32 ($dec) {
        return str_pad(decbin($dec), 32, '0', STR_PAD_LEFT);
    }

    // ipv4_in_range
    // This function takes 2 arguments, an IP address and a "range" in several
    // different formats.
    // Network ranges can be specified as:
    // 1. Wildcard format:     1.2.3.*
    // 2. CIDR format:         1.2.3/24  OR  1.2.3.4/255.255.255.0
    // 3. Start-End IP format: 1.2.3.0-1.2.3.255
    // The function will return true if the supplied IP is within the range.
    // Note little validation is done on the range inputs - it expects you to
    // use one of the above 3 formats.
    public static function ipv4_in_range($ip, $range) {
        if (strpos($range, '/') !== false) {
            // $range is in IP/NETMASK format
            list($range, $netmask) = explode('/', $range, 2);
            if (strpos($netmask, '.') !== false) {
                // $netmask is a 255.255.0.0 format
                $netmask = str_replace('*', '0', $netmask);
                $netmask_dec = ip2long($netmask);
                return ( (ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec) );
            } else {
                // $netmask is a CIDR size block
                // fix the range argument
                $x = explode('.', $range);
                while(count($x)<4) $x[] = '0';
                list($a,$b,$c,$d) = $x;
                $range = sprintf("%u.%u.%u.%u", empty($a)?'0':$a, empty($b)?'0':$b,empty($c)?'0':$c,empty($d)?'0':$d);
                $range_dec = ip2long($range);
                $ip_dec = ip2long($ip);

                # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s
                #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0'));

                # Strategy 2 - Use math to create it
                $wildcard_dec = pow(2, (32-$netmask)) - 1;
                $netmask_dec = ~ $wildcard_dec;

                return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec));
            }
        } else {
            // range might be 255.255.*.* or 1.2.3.0-1.2.3.255
            if (strpos($range, '*') !==false) { // a.b.*.* format
                // Just convert to A-B format by setting * to 0 for A and 255 for B
                $lower = str_replace('*', '0', $range);
                $upper = str_replace('*', '255', $range);
                $range = "$lower-$upper";
            }

            if (strpos($range, '-')!==false) { // A-B format
                list($lower, $upper) = explode('-', $range, 2);
                $lower_dec = (float)sprintf("%u",ip2long($lower));
                $upper_dec = (float)sprintf("%u",ip2long($upper));
                $ip_dec = (float)sprintf("%u",ip2long($ip));
                return ( ($ip_dec>=$lower_dec) && ($ip_dec<=$upper_dec) );
            }
            return false;
        }
    }

    public static function ip2long6($ip) {
        if (substr_count($ip, '::')) {
            $ip = str_replace('::', str_repeat(':0000', 8 - substr_count($ip, ':')) . ':', $ip);
        }

        $ip = explode(':', $ip);
        $r_ip = '';
        foreach ($ip as $v) {
            $r_ip .= str_pad(base_convert($v, 16, 2), 16, 0, STR_PAD_LEFT);
        }

        return base_convert($r_ip, 2, 10);
    }

    // Get the ipv6 full format and return it as a decimal value.
    public static function get_ipv6_full($ip)
    {
        $pieces = explode ("/", $ip, 2);
        $left_piece = $pieces[0];
        $right_piece = $pieces[1];

        // Extract out the main IP pieces
        $ip_pieces = explode("::", $left_piece, 2);
        $main_ip_piece = $ip_pieces[0];
        $last_ip_piece = $ip_pieces[1];

        // Pad out the shorthand entries.
        $main_ip_pieces = explode(":", $main_ip_piece);
        foreach($main_ip_pieces as $key=>$val) {
            $main_ip_pieces[$key] = str_pad($main_ip_pieces[$key], 4, "0", STR_PAD_LEFT);
        }

        // Check to see if the last IP block (part after ::) is set
        $last_piece = "";
        $size = count($main_ip_pieces);
        if (trim($last_ip_piece) != "") {
            $last_piece = str_pad($last_ip_piece, 4, "0", STR_PAD_LEFT);

            // Build the full form of the IPV6 address considering the last IP block set
            for ($i = $size; $i < 7; $i++) {
                $main_ip_pieces[$i] = "0000";
            }
            $main_ip_pieces[7] = $last_piece;
        }
        else {
            // Build the full form of the IPV6 address
            for ($i = $size; $i < 8; $i++) {
                $main_ip_pieces[$i] = "0000";
            }
        }

        // Rebuild the final long form IPV6 address
        $final_ip = implode(":", $main_ip_pieces);

        return ip2long6($final_ip);
    }


    // Determine whether the IPV6 address is within range.
    // $ip is the IPV6 address in decimal format to check if its within the IP range created by the cloudflare IPV6 address, $range_ip.
    // $ip and $range_ip are converted to full IPV6 format.
    // Returns true if the IPV6 address, $ip,  is within the range from $range_ip.  False otherwise.
    public static function ipv6_in_range($ip, $range_ip)
    {
        $pieces = explode ("/", $range_ip, 2);
        $left_piece = $pieces[0];
        $right_piece = $pieces[1];

        // Extract out the main IP pieces
        $ip_pieces = explode("::", $left_piece, 2);
        $main_ip_piece = $ip_pieces[0];
        $last_ip_piece = $ip_pieces[1];

        // Pad out the shorthand entries.
        $main_ip_pieces = explode(":", $main_ip_piece);
        foreach($main_ip_pieces as $key=>$val) {
            $main_ip_pieces[$key] = str_pad($main_ip_pieces[$key], 4, "0", STR_PAD_LEFT);
        }

        // Create the first and last pieces that will denote the IPV6 range.
        $first = $main_ip_pieces;
        $last = $main_ip_pieces;

        // Check to see if the last IP block (part after ::) is set
        $last_piece = "";
        $size = count($main_ip_pieces);
        if (trim($last_ip_piece) != "") {
            $last_piece = str_pad($last_ip_piece, 4, "0", STR_PAD_LEFT);

            // Build the full form of the IPV6 address considering the last IP block set
            for ($i = $size; $i < 7; $i++) {
                $first[$i] = "0000";
                $last[$i] = "ffff";
            }
            $main_ip_pieces[7] = $last_piece;
        }
        else {
            // Build the full form of the IPV6 address
            for ($i = $size; $i < 8; $i++) {
                $first[$i] = "0000";
                $last[$i] = "ffff";
            }
        }

        // Rebuild the final long form IPV6 address
        $first = ip2long6(implode(":", $first));
        $last = ip2long6(implode(":", $last));
        $in_range = ($ip >= $first && $ip <= $last);

        return $in_range;
    }
}

/* These two functions are from Cvolton (github.com/Cvolton) */
/* https://github.com/Cvolton/GMDprivateServer/blob/master/incl/lib/mainLib.php#L511 */

function isCloudFlareIP($ip) {
        $cf_ips = array(
            '173.245.48.0/20',
            '103.21.244.0/22',
            '103.22.200.0/22',
            '103.31.4.0/22',
            '141.101.64.0/18',
            '108.162.192.0/18',
            '190.93.240.0/20',
            '188.114.96.0/20',
            '197.234.240.0/22',
            '198.41.128.0/17',
            '162.158.0.0/15',
            '104.16.0.0/13',
            '104.24.0.0/14',
            '172.64.0.0/13',
            '131.0.72.0/22'
        );
        foreach ($cf_ips as $cf_ip) {
            if (ipInRange::ipv4_in_range($ip, $cf_ip)) {
                return true;
            }
        }
        return false;
}

function getIP(){
        if (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && $this->isCloudFlareIP($_SERVER['REMOTE_ADDR'])) //CLOUDFLARE REVERSE PROXY SUPPORT
            return $_SERVER['HTTP_CF_CONNECTING_IP'];
        if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && ipInRange::ipv4_in_range($_SERVER['REMOTE_ADDR'], '127.0.0.0/8')) //LOCALHOST REVERSE PROXY SUPPORT (7m.pl)
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
        return $_SERVER['REMOTE_ADDR'];
}

我知道代码真的很长(也许里面有一些无用的东西,我没有写程序,我只是融合了它们),所以你可以尝试优化它(比如将 3 个函数融合成一个,只保留你需要的功能)但这(通常)有效。 代码来自 https://github.com/Cvolton/GMDprivateServer/ The Cvolton 的函数: https://github.com/Cvolton/GMDprivateServer/blob/master/incl/lib/mainLib.php#L511 ip_in_range类: https://github.com/Cvolton/GMDprivateServer/blob/master/incl/lib/ip_in_range.php

0赞 Adiyya Tadikamalla 10/25/2023 #37

我已经得到了很多答案,我决定提供改进的代码来获取访问者 IP 地址。

  • 与CloudFlare兼容
  • 获取CloudFlare网络背后的真实访客IP
  • 处理涉及多个代理的情况 (HTTP_X_FORWARDED_FOR)
  • HTTP_X_FORWARDED_FOR可以有多个IP,如“1.1.1.1,2.2.2.2”
  • 如果没有 _SERVER 美元,请工作。
  • 筛选专用 IP 和/或保留 IP
  • 在X_FORWARDED_FOR中处理所有转发的 IP
  • 检查共享 Internet/ISP IP (HTTP_CLIENT_IP)

function GetIP()   {
// Compatible with CloudFlare
// Get real visitor IP behind CloudFlare network
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
    $_SERVER['HTTP_CLIENT_IP'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
}
foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key)
{
    if (array_key_exists($key, $_SERVER) === true)
    {
       // Handle the case where there are multiple proxies involved
       // HTTP_X_FORWARDED_FOR can have multiple ip like '1.1.1.1,2.2.2.2'
        foreach (array_map('trim', explode(',', $_SERVER[$key])) as $ip) 
        {
            // Filter private and/or reserved IPs;
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
            {
                return $ip;
            }
        }
    }  else {
        // Work if $_SERVER was not available.
        foreach (array_map('trim', explode(',', getenv($key))) as $ip)
        {
            // Filter private and/or reserved IPs;
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false)
            {
                return $ip;
            }
        }
    }
}
}