Nginx反向代理獲取客戶端真實IP、域名、協議、端口

Nginx反向代理獲取客戶端真實IP、域名、協議、端口

本地調試因缺少端口不能加載靜態資源,網上搜的都貼邊靠譜,但都沒解決,本章解決了有端口時加載靜態資源問題

附上參考地址:https://www.sunjs.com/article/detail/196ec851e818426ab55b49b6b75cdc44.html

Nginx反向代理後,Servlet應用通過request.getRemoteAddr()取到的IP是Nginx的IP地址,並非客戶端真實IP,通過request.getRequestURL()獲取的域名、協議、端口都是Nginx訪問Web應用時的域名、協議、端口,而非客戶端瀏覽器地址欄上的真實域名、協議、端口

Nginx反向代理後,Servlet應用通過request.getRemoteAddr()取到的IP是Nginx的IP地址,並非客戶端真實IP,通過request.getRequestURL()獲取的域名、協議、端口都是Nginx訪問Web應用時的域名、協議、端口,而非客戶端瀏覽器地址欄上的真實域名、協議、端口。

 

例如在某一臺IP爲10.4.64.22的服務器上,Jetty或者Tomcat端口號爲8080,Nginx端口號80,Nginx反向代理8080端口:

1

2

3

4

5

6

[xml]

server {

    listen 80;

    location / {

        proxy_pass http://127.0.0.1:8080; # 反向代理應用服務器HTTP地址

    }

}

在另一臺機器上用瀏覽器打開http://10.4.64.22/test訪問某個Servlet應用,獲取客戶端IP和URL:

1

2

[java]

System.out.println("RemoteAddr: " + request.getRemoteAddr());

System.out.println("URL: " + request.getRequestURL().toString());

結果是:

1

2

[java]

RemoteAddr: 127.0.0.1

URL: http://127.0.0.1:8080/test

可以發現,Servlet程序獲取到的客戶端IP是Nginx的IP而非瀏覽器所在機器的IP,獲取到的URL是Nginx proxy_pass配置的URL組成的地址,而非瀏覽器地址欄上的真實地址。如果將Nginx用作https服務器反向代理後端的http服務,那麼request.getRequestURL()獲取的URL是http前綴的而非https前綴,無法獲取到瀏覽器地址欄的真實協議。如果此時將request.getRequestURL()獲取得到的URL用作拼接Redirect地址,就會出現跳轉到錯誤的地址,這也是Nginx反向代理時經常出現的一個問題。

 

問題產生的原因

Nginx的反向代理實際上是客戶端和真實的應用服務器之間的一個橋樑,客戶端(一般是瀏覽器)訪問Nginx服務器,Nginx再去訪問Web應用服務器。對於Web應用來說,這次HTTP請求的客戶端是Nginx而非真實的客戶端瀏覽器,如果不做特殊處理的話,Web應用會把Nginx當作請求的客戶端,獲取到的客戶端信息就是Nginx的一些信息。

 

解決方案

解決這個問題要從兩個方面來解決:

 

由於Nginx是代理服務器,所有客戶端請求都從Nginx轉發到Jetty/Tomcat,如果Nginx不把客戶端真實IP、域名、協議、端口告訴Jetty/Tomcat,那麼Jetty/Tomcat應用是永遠不會知道這些信息的,所以需要Nginx配置一些HTTP Header來將這些信息告訴被代理的Jetty/Tomcat;

Jetty/Tomcat這一端,不能再傻乎乎的獲取直接和它連接的客戶端(也就是Nginx)的信息,而是要從Nginx傳遞過來的HTTP Header中獲取客戶端信息。

Nginx

添加以下配置:(題外話,重點在這,因爲別的方法配置頭信息只有地址沒有端口,此方法能添加端口獲取本地真實靜態資源)

1

2

3

4

[xml]

proxy_set_header Host $http_host;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_set_header X-Forwarded-Proto $scheme;

解釋以下上面的配置,以上配置是在Nginx反向代理的時候,添加一些請求Header。

 

Host包含客戶端真實的域名和端口號;

X-Forwarded-Proto表示客戶端真實的協議(http還是https);

X-Real-IP表示客戶端真實的IP;

X-Forwarded-For這個Header和X-Real-IP類似,但它在多層代理時會包含真實客戶端及中間每個代理服務器的IP。

再試一下request.getRemoteAddr()request.getRequestURL()的輸出結果:

1

2

[java]

RemoteAddr: 127.0.0.1

URL: http://10.4.64.22/test

可以發現URL好像已經沒問題了,但是IP還是本地的IP而非真實客戶端IP。但是如果是用Nginx作爲https服務器反向代理到http服務器,會發現瀏覽器地址欄是https前綴但是request.getRequestURL()獲取到的URL還是http前綴,也就是僅僅配置Nginx還不能徹底解決問題。

 

Jetty/Tomcat

如果你在網上搜索“Java如何獲取客戶端真實IP”,搜索到的解決方案大多是通過獲取HTTP請求頭request.getHeader("X-Forwarded-For")request.getHeader("X-Real-IP")來實現,也就是上面在Nginx上配置的Header,這種方案獲取的結果的確是正確的,但是我個人覺得並不優雅。因爲既然Servlet API提供了request.getRemoteAddr()方法獲取客戶端IP,那麼無論有沒有用反向代理對於代碼編寫者來說應該是透明的。下面介紹一種更加優雅的方式。

 

Jetty

在Jetty服務器的jetty.xml文件中,找到httpConfig,加入配置:

1

2

3

4

5

6

7

8

[xml]

<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">

 

    ...

 

  <Call name="addCustomizer">

    <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>

  </Call>

</New>

重新啓動Jetty,再用瀏覽器打開http://10.4.64.22/test測試,結果:

1

2

[java]

RemoteAddr: 10.1.3.7

URL: http://10.4.64.22/test

此時可發現通過request.getRemoteAddr()獲取到的IP不再是127.0.0.1而是客戶端真實IP,request.getRequestURL()獲取的URL也是瀏覽器上的真實URL,如果Nginx作爲https代理,request.getRequestURL()的前綴也會是https。

 

另外,Jetty將這個功能封裝成一個模塊:http-forwarded。如果不想改jetty.xml配置文件的話,也可以啓用http-forwarded模塊來實現。

 

例如可以通過命令行啓動Jetty:

1

[bash]

java -jar start.jar --module=http-forwarded

更多Jetty如何啓用模塊的相關資料可以參考:http://www.eclipse.org/jetty/documentation/current/startup.html

 

Tomcat

和Jetty類似,如果使用Tomcat作爲應用服務器,可以通過配置Tomcat的server.xml文件,在Host元素內最後加入:

1

[xml]

<Valve className="org.apache.catalina.valves.RemoteIpValve" />

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章