public static function getClientIp() { if (getenv('HTTP_CLIENT_IP')) { $ip = getenv('HTTP_CLIENT_IP'); } if (getenv('HTTP_X_REAL_IP')) { $ip = getenv('HTTP_X_REAL_IP'); } elseif (getenv('HTTP_X_FORWARDED_FOR')) { $ip = getenv('HTTP_X_FORWARDED_FOR'); $ips = explode(',', $ip); $ip = $ips[0]; } elseif (getenv('REMOTE_ADDR')) { $ip = getenv('REMOTE_ADDR'); } else { $ip = '0.0.0.0'; } return $ip; }
注意:php
$_SERVER和getenv的區別,getenv不支持IIS的isapi方式運行的php
getenv(「REMOTE_ADDR」)函數在 apache下能正常獲取ip地址,而在iis中沒有做用,而$_SERVER['REMOTE_ADDR']函數,既可在apache中成功獲取訪客的ip地址,在iis下也一樣有效
1、關於 REMOTE_ADDR 這個變量獲取到的是《直接來源》的 IP 地址,所謂《直接來源》指的是直接請求該地址的客戶端 IP 。這個 IP 在單服務器的狀況下,很準確的是客戶端 IP ,沒法僞造。固然並非全部的程序都必定是單服務器,好比在採用負載均衡的狀況(好比採用 haproxy 或者 nginx 進行負載均衡),這個 IP 就是轉發機器的 IP ,由於過程是客戶端->負載均衡->服務端。是由負載均衡直接訪問的服務端而不是客戶端。 2、關於 HTTP_X_FORWARDED_FOR 和 HTTP_CLIENT_IP 基於《一》,在負載均衡的狀況下直接使用 REMOTE_ADDR 是沒法獲取客戶端 IP 的,這就是一個問題,必須解決。因而就衍生出了負載均衡端將客戶端 IP 加入到 HEAD 中發送給服務端,讓服務端能夠獲取到客戶端的真實 IP 。固然也就產生了各位所說的僞造,畢竟 HEAD 除了協議裏固定的那幾個數據,其餘數據都是可自定義的。 3、爲什麼網上找到獲取客戶端 IP 的代碼都要依次獲取 HTTP_CLIENT_IP 、 HTTP_X_FORWARDED_FOR 和 REMOTE_ADDR 基於《一》和《二》以及對程序通用性的考慮,因此才這樣作。 假設你在程序裏直接寫死了 REMOTE_ADDR ,有一天大家的程序須要作負載均衡了,那麼你有得改了。固然若是你想這麼作也行,看我的愛好和應用場景。也能夠封裝一個只有 REMOTE_ADDR 的方法,等到須要的時候改這一個方法就好了。
總結:html
HTTP_CLIENT_IP:
頭是有的,只是未成標準,不必定服務器都實現了。前端
X-Forwarded-For(XFF): 是用來識別經過HTTP代理或負載均衡方式鏈接到Web服務器的客戶端最原始的IP地址的HTTP請求頭字段, 格式:clientip,proxy1,proxy2
nginx
REMOTE_ADDR:
是可靠的, 它是最後一個跟你的服務器握手的IP
,多是用戶的代理服務器,也多是本身的反向代理。sql
X-Forwarded-For
是用於記錄代理信息的,每通過一級代理(匿名代理除外),代理服務器都會把此次請求的來源IP
追加在X-Forwarded-For
中, 而X-Real-IP
,沒有相關標準, 其值在不一樣的代理環境不固定數據庫
關於此的更多討論能夠參考:https://www.douban.com/group/topic/27482290/ apache
生產環境中不少服務器隱藏在負載均衡節點後面,你經過REMOTE_ADDR
只能獲取到負載均衡節點的ip地址,通常的負載均衡節點會把前端實際的ip地址經過HTTP_CLIENT_IP
或者HTTP_X_FORWARDED_FOR
這兩種http頭傳遞過來。json
後端再去讀取這個值就是真實可信的,由於它是負載均衡節點告訴你的而不是客戶端。但當你的服務器直接暴露在客戶端前面的時候,請不要信任這兩種讀取方法,只須要讀取REMOTE_ADDR
就好了segmentfault
延伸閱讀:一張圖解釋負載均衡後端
因此對於咱們獲取用戶的IP,應該截取http_x_forwarded_for的第一個有效IP(非unknown)。
多層代理時,和cdn方式相似。
注意:
不管是REMOTE_ADDR仍是HTTP_FORWARDED_FOR,這些頭消息未必可以取獲得,由於不一樣的瀏覽器不一樣的網絡設備可能發送不一樣的IP頭消息
加入如下代碼防止IP注入攻擊:
// IP地址合法驗證, 防止經過IP注入攻擊 $long = sprintf("%u", ip2long($ip)); $ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
通常獲取IP後更新到數據庫代碼如: $sql="update t_users set login_ip='".get_client_ip()."' where ..." ,而若是接收到的ip地址是: xxx.xxx.xxx.xxx';delete from t_users;-- ,代入參數SQL語句就變成了: "update t_users set login_ip='xxx.xxx.xxx.xxx';delete from t_users;-- where ...
因此獲取IP地址後,務必使用正則等對IP地址的有效性進行驗證,另一定要使用參數化SQL命令
sprintf() 函數把格式化的字符串寫入變量中。
int ip2long ( string $ip_address
) :
返回IP地址轉換後的數字 或 FALSE
若是 ip_address
是無效的。
例子說明打印一個轉換後的地址使用 printf() 在PHP4和PHP5的功能:
<?php $ip = gethostbyname('www.example.com'); $long = ip2long($ip); if ($long == -1 || $long === FALSE) { echo 'Invalid IP, please try again'; } else { echo $ip . "\n"; // 192.0.34.166 echo $long . "\n"; // -1073732954 printf("%u\n", ip2long($ip)); // 3221234342 } ?>
1. 由於PHP的 integer 類型是有符號,而且有許多的IP地址講致使在32位系統的狀況下爲負數, 你須要使用 "%u" 進行轉換經過 sprintf() 或printf() 獲得的字符串來表示無符號的IP地址。
2. ip2long() 將返回 FALSE
在IP是 255.255.255.255 的狀況,版本爲 PHP 5 <= 5.0.2. 在修復後 PHP 5.0.3 會返回 -1 (與PHP4相同).
對於獲取到IP後咱們能夠作一些防刷操做:
//ip限額 $ip = getClientIp(); $ipKey = "activity_key_{$ip}";
if (!frequencyCheckWithTimesInCache($ipKey, $duration, $limitTimes)) { return false; } return true;
// 限制id,在$second時間內,最多請求$times次 public static function frequencyCheckWithTimesInCache($id, $second, $times) { $value = Yii::app()->cache->get($id); if (!$value) { $data[] = time(); Yii::app()->cache->set($id, json_encode($data), $second); return true; } $data = json_decode($value, true); if (count($data) + 1 <= $times) { $data[] = time(); Yii::app()->cache->set($id, json_encode($data), $second); return true; } if (time() - $data[0] > $second) { array_shift($data); $data[] = time(); Yii::app()->cache->set($id, json_encode($data), $second); return true; } return false; }
舉例:
限制每小時請求不超過50次
if (!frequencyCheckWithTimesInCache('times_uid_' . $uid, 3600, 50)) { return '請求過於頻繁'; }
//設備號 一個設備號最多隻能抽獎3次 if(! empty($deviceId)){ $deviceUseChance = Yii::app()->db->createCommand() ->select('count(id)')->from('activity00167_log') ->where('device_id=:deviceId',['deviceId'=>$deviceId]) ->queryScalar(); $deviceChance = 3 - $deviceUseChance; }
對於獲取IP地址還能夠在大數據分析用戶的地理位置,好比作一些精準投放等工做。
以上有理解不正確的歡迎指出討論~
參考文章:
https://www.cnblogs.com/iteemo/p/5062291.html
https://segmentfault.com/q/1010000000686700
http://www.cnblogs.com/sochishun/p/7168860.html