摘要:在有反向代理的情況下,直接使用獲取到的地址是所在服務器的地址,而不是客戶端的。如何防范方法一在直接對外的反向代理服務器上配置如果有多層代理,內層的配置在最外層即直接對外提供服務的使用代替上面的,可以防止偽造。
問題背景
在Web應用開發中,經常會需要獲取客戶端IP地址。一個典型的例子就是投票系統,為了防止刷票,需要限制每個IP地址只能投票一次。
如何獲取客戶端IP在Java中,獲取客戶端IP最直接的方式就是使用request.getRemoteAddr()。這種方式能獲取到連接服務器的客戶端IP,在中間沒有代理的情況下,的確是最簡單有效的方式。但是目前互聯網Web應用很少會將應用服務器直接對外提供服務,一般都會有一層Nginx做反向代理和負載均衡,有的甚至可能有多層代理。在有反向代理的情況下,直接使用request.getRemoteAddr()獲取到的IP地址是Nginx所在服務器的IP地址,而不是客戶端的IP。
HTTP協議是基于TCP協議的,由于request.getRemoteAddr()默認獲取到的是TCP層直接連接的客戶端的IP,對于Web應用服務器來說直接連接它的客戶端實際上是Nginx,也就是TCP層是拿不到真實客戶端的IP。
為了解決上面的問題,很多HTTP代理會在HTTP協議頭中添加X-Forwarded-For頭,用來追蹤請求的來源。X-Forwarded-For的格式如下:
X-Forwarded-For: client1, proxy1, proxy2
X-Forwarded-For包含多個IP地址,每個值通過逗號+空格分開,最左邊(client1)是最原始客戶端的IP地址,中間如果有多層代理,每一層代理會將連接它的客戶端IP追加在X-Forwarded-For右邊。
下面就是一種常用的獲取客戶端真實IP的方法,首先從HTTP頭中獲取X-Forwarded-For,如果X-Forwarded-For頭存在就按逗號分隔取最左邊第一個IP地址,不存在直接通過request.getRemoteAddr()獲取IP地址:
public String getClientIp(HttpServletRequest request) { String xff = request.getHeader("X-Forwarded-For"); if (xff == null) { return request.getRemoteAddr(); } else { return xff.contains(",") ? xff.split(",")[0] : xff; } }
另外,要讓Nginx支持X-Forwarded-For頭,需要配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
$proxy_add_x_forwarded_for會將和Nginx直接連接的客戶端IP追加在請求原有X-Forwarded-For值的右邊。
偽造X-Forwarded-For一般的客戶端(例如瀏覽器)發送HTTP請求是沒有X-Forwarded-For頭的,當請求到達第一個代理服務器時,代理服務器會加上X-Forwarded-For請求頭,并將值設為客戶端的IP地址(也就是最左邊第一個值),后面如果還有多個代理,會依次將IP追加到X-Forwarded-For頭最右邊,最終請求到達Web應用服務器,應用通過獲取X-Forwarded-For頭取左邊第一個IP即為客戶端真實IP。
但是如果客戶端在發起請求時,請求頭上帶上一個偽造的X-Forwarded-For,由于后續每層代理只會追加而不會覆蓋,那么最終到達應用服務器時,獲取的左邊第一個IP地址將會是客戶端偽造的IP。也就是上面的Java代碼中getClientIp()方法獲取的IP地址很有可能是偽造的IP地址,如果一個投票系統用這種方式做的IP限制,那么很容易會被刷票。
偽造X-Forwarded-For頭的方法很簡單,例如Postman就可以輕松做到:
當然你也可以寫一段刷票程序或者腳本,每次請求時添加X-Forwarded-For頭并隨機生成一個IP來實現刷票的目的。
如何防范 方法一在直接對外的Nginx反向代理服務器上配置:
proxy_set_header X-Forwarded-For $remote_addr;
如果有多層Nginx代理,內層的Nginx配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
在最外層Nginx(即直接對外提供服務的Nginx)使用$remote_addr代替上面的$proxy_add_x_forwarded_for,可以防止偽造X-Forwarded-For。$proxy_add_x_forwarded_for會在原有X-Forwarded-For上追加IP,這就相當于給了偽造X-Forwarded-For的機會。而$remote_addr是獲取的是直接TCP連接的客戶端IP,這個是無法偽造的,即使客戶端偽造也會被覆蓋掉,而不是追加。
需要注意的是,如果有多層代理,只在直接對外訪問的Nginx上配置X-Forwarded-For為$remote_addr,內層的Nginx還是要配置為$proxy_add_x_forwarded_for,不然內層的Nginx又會覆蓋掉客戶端的真實IP。
完成以上配置后,業務代碼中再通過上面的getClientIp()方法,獲取X-Forwarded-For最左邊的IP地址即為真實的客戶端地址,且客戶端也無法偽造。
方法二Tomcat服務器解決方案:org.apache.catalina.valves.RemoteIpValve
RemoteIpValve可以替換Servlet API中request.getRemoteAddr()方法的實現,讓request.getRemoteAddr()方法從X-Forwarded-For頭中獲取IP地址。也就是在業務代碼中不需要再自己實現類似于上面的getClientIp()方法來從X-Forwarded-For中獲取IP,而是直接使用request.getRemoteAddr()方法。想要使用RemoteIpValve,僅需要在Tomcat配置文件server.xml中Host元素內末尾加上:
RemoteIpValve有一套防止偽造X-Forwarded-For的機制,實現思路:遍歷X-Forwarded-For頭中的IP地址,和方法一不同的是,不是直接取左邊第一個IP,而是從右向左遍歷。遍歷時可以根據正則表達式剔除掉內網IP和已知的代理服務器本身的IP(例如192.168開頭的IP),那么拿到的第一個非剔除IP就會是一個可信任的客戶端IP。這種方法的巧妙之處在于,即使偽造X-Forwarded-For,那么請求到達應用服務器時,偽造的IP也會在X-Forwarded-For值的左邊,真實的IP為放到右邊的某個位置,從右向左遍歷就可以避免取到這些偽造的IP地址。
方法三Node.js 框架 Egg.js 的解決方案:https://eggjs.org/zh-cn/tutor...
Egg.js 可通過設置maxProxyCount指定代理層數,然后取X-Forwarded-For頭中從右往左數第maxProxyCount個IP即為真實 IP 地址,如果有偽造 IP 地址了必然在最左邊,就會被忽略掉。
關注我文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/40422.html
摘要:網絡黑白一書所抄襲的文章列表這本書實在是垃圾,一是因為它的互聯網上的文章拼湊而成的,二是因為拼湊水平太差,連表述都一模一樣,還抄得前言不搭后語,三是因為內容全都是大量的科普,不涉及技術也沒有干貨。 《網絡黑白》一書所抄襲的文章列表 這本書實在是垃圾,一是因為它的互聯網上的文章拼湊而成的,二是因為拼湊水平太差,連表述都一模一樣,還抄得前言不搭后語,三是因為內容全都是大量的科普,不涉及技術...
閱讀 3465·2023-04-25 18:52
閱讀 2478·2021-11-22 15:31
閱讀 1217·2021-10-22 09:54
閱讀 3003·2021-09-29 09:42
閱讀 602·2021-09-26 09:55
閱讀 905·2021-09-13 10:28
閱讀 1092·2019-08-30 15:56
閱讀 2104·2019-08-30 15:55