我所在的專案模組,有個功能點需要獲取使用者即客戶端的IP地址,用以實現後續的功能,已開始使用PHP的$SERVER超級全域性變數,但是獲取到的不是很準確,腦子裡一直有個思想【PHP是執行於服務端的指令碼,理論上沒辦法拿到客戶端的ip】,這也導致我遇到這個問題理所當然地的認為應該用JS去實現。於是 前端給出方案去獲取,但是由於前端的方案也不是很成熟,所以不穩定,時常出現獲取不到IP的情況,尤其最近使用者更新了Google瀏覽器,這種不穩定越發明顯。
今天 公司大佬 說 是伺服器端是可以拿到的,於是看了CI框架(公司用的CI框架)的相關配置與方法,原來真的可以~ 出糗了 哈哈~
一 獲取IP相關方法介紹
php的超級全域性變數 $_SERVER,我們來了解其中一些引數的釋義
REMOTE_ADDR: 客戶端,如果對方通過代理服務訪問,那麼拿到的就是代理服務的IP了。比較難於篡改
HTTP_CLIENT_IP: 是代理伺服器傳送的HTTP頭部,可以偽造,如果是 超級匿名代理 那麼返回 空
HTTP_X_FORWARDED_FOR: = 公網客戶端IP,proxy1,proxy2 所有的IP通過逗號分隔,可以偽造
HTTP_X_CLIENT_IP: 未知
HTTP_X_CLUSTER_CLIENT_IP: 未知
#外網訪問示例 A
{
"input_ip":"220.222.222.22", #使用者公網的客戶端IP(CI框架方法獲得)
"REMOTE_ADDR":"10.22.122.122", #拿到的是代理IP
"HTTP_X_FORWARDED_FOR":"220.222.222.22, 192.168.22.22, 172.22.22.22",#客戶端公網IP 加上兩個是代理IP
"HTTP_CLIENT_IP":null,
"HTTP_X_CLIENT_IP":null,
"HTTP_X_CLUSTER_CLIENT_IP":null
}
# 區域網訪問示例 B
{
"input_ip":"10.28.22.122",#使用者區域網的客戶端IP(CI框架方法獲得)
"REMOTE_ADDR":"10.22.122.122",#代理IP
"HTTP_X_FORWARDED_FOR":"10.28.22.122",#等於使用者區域網的客戶端IP
"HTTP_CLIENT_IP":null,
"HTTP_X_CLIENT_IP":null,
"HTTP_X_CLUSTER_CLIENT_IP":null
}
二 常見獲取IP方法
function getIPaddress(){
$IPaddress='';
if (isset($_SERVER))
{
if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
{
#優先使用 HTTP_X_FORWARDED_FOR,從示例A看出 此值有可能是一個逗號分割的多個IP ,那麼這樣直接獲取是否欠考慮?
$IPaddress = $_SERVER["HTTP_X_FORWARDED_FOR"];
}
else if (isset($_SERVER["HTTP_CLIENT_IP"]))
{
$IPaddress = $_SERVER["HTTP_CLIENT_IP"];
}
else
{
$IPaddress = $_SERVER["REMOTE_ADDR"];
}
}
else
{
if (getenv("HTTP_X_FORWARDED_FOR"))
{
# getenv() 獲取系統的環境變數
$IPaddress = getenv("HTTP_X_FORWARDED_FOR");
} else if (getenv("HTTP_CLIENT_IP"))
{
$IPaddress = getenv("HTTP_CLIENT_IP");
}
else
{
$IPaddress = getenv("REMOTE_ADDR");
}
}
return $IPaddress;
}
三 針對CI框架 如何獲取客戶端IP呢?
CI框架 在系統的核心檔案system\core\Input.php 檔案中有個 ip_address()的方法,下面我們來看下這個方法的程式碼:
config.php 檔案裡面關於代理的配置
$config['proxy_ips'] = '192.168.111.111';
或者
$config['proxy_ips'] = array('10.11.111.111', '10.22.222.222');
protected $ip_address = FALSE;
/**
* 獲取客戶端IP
/
public function ip_address() {
# 如果已經存在 ip_address 則直接返回
if ($this->ip_address !== FALSE)
{
return $this->ip_address;
}
# 獲取config.php 配置的代理配置
$proxy_ips = config_item('proxy_ips');
# 如果 代理配置不為空 並且 不是陣列,則將 代理字串轉換為陣列
if ( ! empty($proxy_ips) && ! is_array($proxy_ips))
{
$proxy_ips = explode(',', str_replace(' ', '', $proxy_ips));
}
#將ip_address 賦值為 $_SERVER['REMOTE_ADDR']
$this->ip_address = $this->server('REMOTE_ADDR');
# 如果 代理配置資料不為空
if ($proxy_ips)
{
foreach (array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP') as $header) {
# 如果$_SERVER[$header] 的值不為空,則賦值給 $spoof
if (($spoof = $this->server($header)) !== NULL)
{
if ( ! $this->valid_ip($spoof))
{
# 如果 $spoof 不是合法的ip地址,則$spoof =NULL
$spoof = NULL;
}
else
{
# 否則 跳出迴圈
break;
}
}
}
# 如果 $spoof 為 true
if ($spoof)
{
# 迴圈 代理配置陣列
for ($i = 0, $c = count($proxy_ips); $i < $c; $i++) {
#檢查IP是否有子網
if (strpos($proxy_ips[$i], '/') === FALSE)
{
# 如果代理地址和上面的ip_address相等,即REMOTE_ADDR= 代理IP,則取前面的 $spoof 值
if ($proxy_ips[$i] === $this->ip_address)
{
$this->ip_address = $spoof;
break; #跳出迴圈
}
continue;
}
#如果有子網
isset($separator) OR $separator = $this->valid_ip($this->ip_address, 'ipv6') ? ':' : '.';
#如果代理IP不滿足IPV6協議,則進行一下迴圈,否則繼續執行
if (strpos($proxy_ips[$i], $separator) === FALSE)
{
continue;
}
// Convert the REMOTE_ADDR IP address to binary, if needed
if ( ! isset($ip, $sprintf))
{
if ($separator === ':')
{
// Make sure we're have the "full" IPv6 format
$ip = explode(':',str_replace('::',str_repeat(':', 9 - substr_count($this->ip_address, ':')),$this->ip_address ));
for ($j = 0; $j < 8; $j++)
{
$ip[$j] = intval($ip[$j], 16);
}
$sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
}
else
{
$ip = explode('.', $this->ip_address);
$sprintf = '%08b%08b%08b%08b';
}
$ip = vsprintf($sprintf, $ip);
}
// Split the netmask length off the network address
sscanf($proxy_ips[$i], '%[^/]/%d', $netaddr, $masklen);
// Again, an IPv6 address is most likely in a compressed form
if ($separator === ':')
{
$netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
for ($i = 0; $i < 8; $i++)
{
$netaddr[$i] = intval($netaddr[$i], 16);
}
}
else
{
$netaddr = explode('.', $netaddr);
}
// Convert to binary and finally compare
if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0)
{
$this->ip_address = $spoof;
break;
}
}
}
}
if ( ! $this->valid_ip($this->ip_address))
{
return $this->ip_address = '0.0.0.0';
}
return $this->ip_address;
}
四 針對 Laravel 如何獲取客戶端IP呢?
Laravel 有個 getClientIp();
public function getClientIp()
{
$ipAddresses = $this->getClientIps();
return $ipAddresses[0];
}
public function getClientIps()
{
# 通過`$_SERVER['REMOTE_ADDR']`這個變數來獲取客戶端IP的!然後判斷是不是來自可信任的代理,因為靜態變數`$trustedProxies`預設情況下沒有設定,但是也可以通過 setTrustedProxies 方法去設定,所以`isFromTrustedProxy()`方法返回的值是`false`,所以直接返回了`$_SERVER['REMOTE_ADDR']`獲取到的值,感覺和CI的邏輯大同小異。
$ip = $this->server->get('REMOTE_ADDR');
if (!$this->isFromTrustedProxy()) {
return [$ip];
}
# 然後
return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip];
}
public function isFromTrustedProxy()
{
return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
}
五 Nginx 如何配置 試服務端可以獲取到客戶端IP?
首先我們的前端虛擬主機配置檔案如下
location /api {
proxy_pass http://your-api-domain.com;
#proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-Port $remote_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header HTTP_X_FORWARDED_FOR $remote_addr;
proxy_redirect default;
}
然後在API
虛擬主機配置檔案中加入下面一行:
server {
# ...
set_real_ip_from xxxxx;
real_ip_header X-Forwarded-For;
}
六 代理知識點
HTTP 代理可以分為 透明代理,匿名代理,高度匿名代理
透明代理:對方伺服器可以知道你使用了代理,並且也知道你的真實IP,那麼透明代理訪問對方伺服器帶了HTTP頭資訊如下:
REMOTE_ADDR = 代理伺服器IP
HTTP_VIA = 代理伺服器IP
HTTP_X_FORWARDED_FOR = 你的真實IP
所以透明代理還是將你的真實IP傳送給了地方伺服器,因此無法達到隱藏身份的目的
匿名代理:對方伺服器可以知道你使用了代理,但是不知道你的真實IP,匿名 代理訪問對方伺服器所帶的HTTP頭資訊如下:
REMOTE_ADDR = 代理伺服器IP
HTTP_VIA = 代理伺服器IP
HTTP_X_FORWARDED_FOR = 代理伺服器IP
匿名代理隱藏了你的真實IP,但是像對方透露了你是使用代理伺服器訪問他們的。
高度匿名代理:對方伺服器不知道你使用了代理,更不知道你的真實IP,高度寧代理訪問對方伺服器所帶的HTTP頭資訊如下:
REMOTE_ADDR = 代理伺服器IP
HTTP_VIA 不顯示
HTTP_X_FORWARDED_FOR 不顯示
因此 高度匿名代理隱藏了你的真實IP,同時訪問物件也不知道你使用了代理,因此隱蔽度最高。
未完待續.....