網頁默認是進行http連接了,http協議即超文本傳送協議(Hypertext Transfer Protocol ),是工作TCP協議之上的協議
tcp連接需要三次握手,也就是調用底層的socket進行連接確認。而socket連接需要知道通信雙方的ip地址和端口才可以進行數據的正確傳輸。
如真實ip:223.104.1.240 廣東省廣州市 移動 訪問域名www.zwh.com nginx所在服務器193.112.28.110 這個域名和nginx所在服務器綁定, 客戶端訪問時,如www.zwh.com/項目名/index.jsp 這個訪問了是nginx的80端口,然后nginx再將這個請求重定向到localhost:8080/項目名/index.jsp 而nginx在將請求進行重定向時會在請求頭header中增加一個x-forwarded-for信息,用以跟蹤原有客戶端ip地址和原來客戶端請求的服務器地址 存放的就是x-forwarded-for:223.104.1.240 對應java方法來說,request.getRemoteAddr(),因為直接請求的客戶端是nginx,在同一服務器上,所以這個方法獲取到的ip地址是127.0.0.1或者193.112.28.110
location / { proxy_redirect off; proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
繼續使用上面數據
在有多個代理服務器的情況下,會將代理服務器的X-Forwarded-For頭部的ip地址和客戶端的ip地址相加, 即:X-Forwarded-For請求頭部header的ip字符串 + 直接向nginx服務器發送請求的客戶端地址 換個表達請求的過程是client--->nginx1--->nginx2---->tomcat 而假設client:ip1, nginx1:ip2, nginx2:ip3, tomcat:ip4 那么請求request到tomcat時, 請求頭部header中的X-Forwarded-For信息是=ip1,ip2,ip3
簡單了方法
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; }
上面三個請求頭,大部分的代理或者網關都會加上x-forwarded-for:clientip,proxy1ip,proxy2ip
Proxy-Client-IP這個一般是apache,http服務器才會有,經過apache代理時,apache會在請求頭加上x-forwarded-for和Proxy-Client-IP
WL-Proxy-Client-IP這個一般是weblogic才會加上的頭部
注意:
1、這些請求頭都不是http協議里的標准請求頭,也就是說這個是各個代理服務器自己規定的表示客戶端地址的請求頭。如果哪天有一個代理服務器軟件用oooo-client-ip這個請求頭代表客戶端請求,那上面的代碼就不行了。
2、這些請求頭不是代理服務器一定會帶上的,網絡上的很多匿名代理就沒有這些請求頭,所以獲取到的客戶端ip不一定是真實的客戶端ip。代理服務器一般都可以自定義請求頭設置。
3、即使請求經過的代理都會按自己的規范附上代理請求頭,上面的代碼也不能確保獲得的一定是客戶端ip。不同的網絡架構,判斷請求頭的順序是不一樣的。
4、最重要的一點,請求頭都是可以偽造的。如果一些對客戶端校驗較嚴格的應用(比如投票)要獲取客戶端ip,應該直接使用ip = request.getRemoteAddr (),雖然獲取到的可能是代理的ip而不是客戶端的ip,但這個獲取到的ip基本上是不可能偽造的,也就杜絕了刷票的可能。(有分析說arp欺騙+syn有可能偽造此ip,如果真的可以,這是所有基於TCP協議都存在的漏洞),這個ip是tcp連接里的ip。
鏈接
ip工具類
import javax.servlet.http.HttpServletRequest; /** * IP地址工具類 * @author xudongdong * */ public class IpUtil { /** * 私有化構造器 */ private IpUtil() { } /** * 獲取真實IP地址 * <p>使用getRealIP代替該方法</p> * @param request req * @return ip */ @Deprecated public static String getClinetIpByReq(HttpServletRequest request) { // 獲取客戶端ip地址 String clientIp = request.getHeader("x-forwarded-for"); if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) { clientIp = request.getHeader("Proxy-Client-IP"); } if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) { clientIp = request.getHeader("WL-Proxy-Client-IP"); } if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) { clientIp = request.getRemoteAddr(); } /* * 對於獲取到多ip的情況下,找到公網ip. */ String sIP = null; if (clientIp != null && !clientIp.contains("unknown") && clientIp.indexOf(",") > 0) { String[] ipsz = clientIp.split(","); for (String anIpsz : ipsz) { if (!isInnerIP(anIpsz.trim())) { sIP = anIpsz.trim(); break; } } /* * 如果多ip都是內網ip,則取第一個ip. */ if (null == sIP) { sIP = ipsz[0].trim(); } clientIp = sIP; } if (clientIp != null && clientIp.contains("unknown")){ clientIp =clientIp.replaceAll("unknown,", ""); clientIp = clientIp.trim(); } if ("".equals(clientIp) || null == clientIp){ clientIp = "127.0.0.1"; } return clientIp; } /** * 判斷IP是否是內網地址 * @param ipAddress ip地址 * @return 是否是內網地址 */ public static boolean isInnerIP(String ipAddress) { boolean isInnerIp; long ipNum = getIpNum(ipAddress); /** 私有IP:A類 10.0.0.0-10.255.255.255 B類 172.16.0.0-172.31.255.255 C類 192.168.0.0-192.168.255.255 當然,還有127這個網段是環回地址 **/ long aBegin = getIpNum("10.0.0.0"); long aEnd = getIpNum("10.255.255.255"); long bBegin = getIpNum("172.16.0.0"); long bEnd = getIpNum("172.31.255.255"); long cBegin = getIpNum("192.168.0.0"); long cEnd = getIpNum("192.168.255.255"); isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) || ipAddress.equals("127.0.0.1"); return isInnerIp; } private static long getIpNum(String ipAddress) { String[] ip = ipAddress.split("\\."); long a = Integer.parseInt(ip[0]); long b = Integer.parseInt(ip[1]); long c = Integer.parseInt(ip[2]); long d = Integer.parseInt(ip[3]); return a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d; } private static boolean isInner(long userIp, long begin, long end) { return (userIp >= begin) && (userIp <= end); } public static String getRealIP(HttpServletRequest request){ // 獲取客戶端ip地址 String clientIp = request.getHeader("x-forwarded-for"); if (clientIp == null || clientIp.length() == 0 || "unknown".equalsIgnoreCase(clientIp)) { clientIp = request.getRemoteAddr(); } String[] clientIps = clientIp.split(","); if(clientIps.length <= 1) return clientIp.trim(); // 判斷是否來自CDN if(isComefromCDN(request)){ if(clientIps.length>=2) return clientIps[clientIps.length-2].trim(); } return clientIps[clientIps.length-1].trim(); } private static boolean isComefromCDN(HttpServletRequest request) { String host = request.getHeader("host"); return host.contains("www.189.cn") ||host.contains("shouji.189.cn") || host.contains( "image2.chinatelecom-ec.com") || host.contains( "image1.chinatelecom-ec.com"); } }