JAVA獲取真實IP地址:你獲取的IP地址真實嗎?
阿新 • • 發佈:2019-02-17
想必大家對這段程式碼並不陌生:
public String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; }
是的,你搜索到的“java獲取真實IP地址”大多都是如此。但是,以上程式碼真的對嗎?
那麼我們看一下具體的程式碼。如上,判斷ip地址的優先順序是"x-forwarded-for">"Proxy-Client-IP">"WL-Proxy-Client-IP">request.getRemoteAddr(),其中帶引號的都是從header中獲取的。
等等!我們都知道header中的值是可以更改的。
比如:
$.ajax({ type : "GET", headers : {"X-Forwarded-For":randomIp,"WL-Proxy-Client-IP":randomIp}, contentType : 'application/x-www-form-urlencoded;charset=utf-8', url : url, data:params, dataType : "text", success : function(data) { count++; console.log("時間:【"+new Date()+"】 執行成功:【"+count+"】次:"+data); if(max>0){ setTimeout(function wait(){ console.log("等待"+(timeWait)+"ms ..."); vote(max,getRandomNum(maxWait,minWait)); },timeWait); } } });
程式碼出自:https://github.com/caiyongji/vote-2.0/blob/master/Vote-2.0.js
其中headers屬性X-Forwarded-For,WL-Proxy-Client-IP不就是被更改了嗎?那麼,為什麼會有這個版本的“java獲取真實IP地址”的方法呢?並且搜尋引擎所能檢索到的結果大多都是這一個?
打個比方說,如果這個解決辦法是一本祕籍的話,那麼,我們找到的只是“java獲取真實IP地址”殘卷。
而剩下的部分在這裡:
#Nginx 設定 location ~ ^/static { proxy_pass ....; proxy_set_header X-Forward-For $remote_addr ; }
這段配置是在前端Nginx反向代理上的(其他反向代理請自行搜尋),這段配置作的事情是將X-Forward-For替換為remote_addr,再將X-Forward-For在內網各伺服器間安全傳輸。
這裡我再針對TCP/IP多做一些解釋,眾所周知TCP/IP建立連線時需要三次握手的,並且,只有知道了client端請求的IP地址,server端的資料才能返回給client,所以client想要獲取到資料就必須提供真實的IP(DDOS攻擊除外),而request.getRemoteAddr()獲取的就是使用者最真實的IP。
那麼為什麼不直接使用使用request.getRemoteAddr()這個方法呢?
如果沒有反向代理的話當然可行。但是出於安全原因,現在大多數的服務都使用代理伺服器(如Nginx,代理伺服器可以理解為使用者和伺服器之間的中介,雙方都可信任。),而使用者對代理伺服器發起的HTTP請求,代理伺服器對服務叢集中的真實部署的對應服務進行“二次請求”,所以最終獲取的IP是代理伺服器在內網中的ip地址,如192.168.xx.xx/10.xx.xx.xx等等。
所以在使用了反向代理的情況下,request.getRemoteAddr()獲取的是反響代理在內網中的ip地址。所以在反向代理中將X-Forward-For替換為remote_addr,即,真實的IP地址。之後在內網中獲取的x-forwarded-for便是真實的ip地址了。
最後給出完整解決方案(Nginx為例):- JAVA部分(自外國朋友Bashan):
public class IpUtils {
public static final String _255 = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
public static final Pattern pattern = Pattern.compile("^(?:" + _255 + "\\.){3}" + _255 + "$");
public static String longToIpV4(long longIp) {
int octet3 = (int) ((longIp >> 24) % 256);
int octet2 = (int) ((longIp >> 16) % 256);
int octet1 = (int) ((longIp >> 8) % 256);
int octet0 = (int) ((longIp) % 256);
return octet3 + "." + octet2 + "." + octet1 + "." + octet0;
}
public static long ipV4ToLong(String ip) {
String[] octets = ip.split("\\.");
return (Long.parseLong(octets[0]) << 24) + (Integer.parseInt(octets[1]) << 16)
+ (Integer.parseInt(octets[2]) << 8) + Integer.parseInt(octets[3]);
}
public static boolean isIPv4Private(String ip) {
long longIp = ipV4ToLong(ip);
return (longIp >= ipV4ToLong("10.0.0.0") && longIp <= ipV4ToLong("10.255.255.255"))
|| (longIp >= ipV4ToLong("172.16.0.0") && longIp <= ipV4ToLong("172.31.255.255"))
|| longIp >= ipV4ToLong("192.168.0.0") && longIp <= ipV4ToLong("192.168.255.255");
}
public static boolean isIPv4Valid(String ip) {
return pattern.matcher(ip).matches();
}
public static String getIpFromRequest(HttpServletRequest request) {
String ip;
boolean found = false;
if ((ip = request.getHeader("x-forwarded-for")) != null) {
StrTokenizer tokenizer = new StrTokenizer(ip, ",");
while (tokenizer.hasNext()) {
ip = tokenizer.nextToken().trim();
if (isIPv4Valid(ip) && !isIPv4Private(ip)) {
found = true;
break;
}
}
}
if (!found) {
ip = request.getRemoteAddr();
}
return ip;
}
}
- Nginx部分(自月影無痕):
location ~ ^/static {
proxy_pass ....;
proxy_set_header X-Forward-For $remote_addr ;
}