發生的場景:服務器端接收客戶端請求的時候,通常須要進行簽名驗證,客戶端IP限定等狀況,在進行客戶端IP限定的時候,須要首先獲取該真實的IP。通常分爲兩種狀況:html
方式1、客戶端未通過代理,直接訪問服務器端(nginx,squid,haproxy);nginx
方式2、客戶端經過多級代理,最終到達服務器端(nginx,squid,haproxy);web
客戶端請求信息都包含在HttpServletRequest中,能夠經過方法getRemoteAddr()得到該客戶端IP。此時若是在使用方式一形式,能夠直接得到該客戶端真實IP。而若是是方式二中經過代理的形式,此時通過多級反向的代理,經過方法getRemoteAddr()得不到客戶端真實IP,能夠經過x-forwarded-for得到轉發後請求信息。當客戶端請求被轉發,IP將會追加在其後並以逗號隔開,例如:10.47.103.13,4.2.2.2,10.96.112.230。apache
請求中的參數:服務器
request.getHeader("x-forwarded-for") : 10.47.103.13,4.2.2.2,10.96.112.230網絡
request.getHeader("X-Real-IP") : 10.47.103.13架構
request.getRemoteAddr():10.96.112.230負載均衡
客戶端訪問通過轉發,IP將會追加在其後並以逗號隔開。最終準確的客戶端信息爲:tcp
- x-forwarded-for 不爲空,則爲逗號前第一個IP ;
- X-Real-IP不爲空,則爲該IP ;
- 不然爲getRemoteAddr() ;
代碼示例:post
/** * 獲取用戶真實IP地址,不使用request.getRemoteAddr()的緣由是有可能用戶使用了代理軟件方式避免真實IP地址, * 但是,若是經過了多級反向代理的話,X-Forwarded-For的值並不止一個,而是一串IP值 * * @return ip */ private String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); System.out.println("x-forwarded-for ip: " + ip); if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { // 屢次反向代理後會有多個ip值,第一個ip纔是真實ip if( ip.indexOf(",")!=-1 ){ ip = ip.split(",")[0]; } } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); System.out.println("Proxy-Client-IP ip: " + ip); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); System.out.println("WL-Proxy-Client-IP ip: " + ip); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); System.out.println("HTTP_CLIENT_IP ip: " + ip); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); System.out.println("HTTP_X_FORWARDED_FOR ip: " + ip); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); System.out.println("X-Real-IP ip: " + ip); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); System.out.println("getRemoteAddr ip: " + ip); } System.out.println("獲取客戶端ip: " + ip); return ip; }
上面代碼中這些請求頭的大體意思:
- X-Forwarded-For
這是一個 Squid 開發的字段,只有在經過了HTTP代理或者負載均衡服務器時纔會添加該項。
格式爲X-Forwarded-For:client1,proxy1,proxy2,通常狀況下,第一個ip爲客戶端真實ip,後面的爲通過的代理服務器ip。如今大部分的代理都會加上這個請求頭。
- Proxy-Client-IP/WL- Proxy-Client-IP
這個通常是通過apache http服務器的請求才會有,用apache http作代理時通常會加上Proxy-Client-IP請求頭,而WL-Proxy-Client-IP是他的weblogic插件加上的頭。
- HTTP_CLIENT_IP
有些代理服務器會加上此請求頭。
- X-Real-IP
nginx代理通常會加上此請求頭。
若是使用的是Druid鏈接池,能夠參考使用:com.alibaba.druid.util.DruidWebUtils#getRemoteAddr方法,但這個是通過多級代理的IP地址,須要本身處理下獲取第一個。
有幾點要注意
-
這些請求頭都不是http協議裏的標準請求頭,也就是說這個是各個代理服務器本身規定的表示客戶端地址的請求頭。若是哪天有一個代理服務器軟件用oooo-client-ip這個請求頭表明客戶端請求,那上面的代碼就不行了。
-
這些請求頭不是代理服務器必定會帶上的,網絡上的不少匿名代理就沒有這些請求頭,因此獲取到的客戶端ip不必定是真實的客戶端ip。代理服務器通常均可以自定義請求頭設置。
-
即便請求通過的代理都會按本身的規範附上代理請求頭,上面的代碼也不能確保得到的必定是客戶端ip。不一樣的網絡架構,判斷請求頭的順序是不同的。
-
最重要的一點,請求頭都是能夠僞造的。若是一些對客戶端校驗較嚴格的應用(好比投票)要獲取客戶端ip,應該直接使用ip=request.getRemoteAddr(),雖然獲取到的多是代理的ip而不是客戶端的ip,但這個獲取到的ip基本上是不可能僞造的,也就杜絕了刷票的可能。(有分析說arp欺騙+syn有可能僞造此ip,若是真的能夠,這是全部基於TCP協議都存在的漏洞),這個ip是tcp鏈接裏的ip。
參考
http://blog.csdn.net/sgx425021234/article/details/19043459
http://blog.csdn.net/fengwind1/article/details/51992528