-
代理和獲取規范
-
原則
- F5第一層代理,Nginx作為第二層代理,最終到web應用,如示例:F5->Nginx->Web
- 第一層代理需要把源IP設置到X-Real-IP,二級代理需把一級代理地址寫入X-Forwarded-For
-
名詞解釋
- X-Real-IP: 自定義請求頭中字段
- X-Forwarded-For:一個 HTTP 擴展頭部,各大 HTTP 代理、負載均衡等轉發服務廣泛使用,並被寫入 RFC 7239(Forwarded HTTP Extension)標准之中
-
定義規范的背景
- X-Real-IP和X-Forwarded-For都可以被客戶端隨意偽造,但是自定義X-Real-IP只能存在一個值,X-Forwarded-For字段可以存在多個以逗號分割,代理服務器只能追加,不能確定源IP地址寫入的具體順序
- Remote Address 無法偽造,因為建立 TCP 連接需要三次握手,如果偽造了源 IP,無法建立 TCP 連接,更不會有后面的 HTTP 請求
- 基於以上2點,需要一級代理服務器把源IP寫入X-Real-IP,各級代理需把上級代理地址寫入X-Forwarded-For中,追蹤代理調用鏈
-
F5地址轉換規范
- 在F5配置界面中,把源 IP寫入參數: X-Real-IP
-
Nginx地址轉換配置模板:
Nginx配置server {
# 監聽端口
listen 7777;
server_name localhost;
charset utf-8;
location /{
# 轉發地址
proxy_pass http://10.19.205.58:7777;
proxy_set_header Host $host;
# 只有一層代理時候開啟,源地址放入X-Real-Ip
# proxy_set_header X-Real-IP $remote_addr;
# Nginx作為代理必須開啟,源地址和代理服務器代理地址放入X-Forwarded-For字段
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
-
java代碼獲取源地址
public
String getRealIp(HttpServletRequest request){
// 1. 獲取真實地址
String realIp = request.getHeader(
"X-Real-IP"
);
// 2. 非正常地址或者異常調用
if
(isIlegalHost(realIp)) {
// 2.1 獲取X-Forwarded-For地址組
String forwardIps= request.getHeader(
"X-Forwarded-For"
);
if
(forwardIps !=
null
){
String[] forwardIpArray = forwardIps.split(
","
);
// 2.2 循環X-Forwarded-For 的地址,返回第一個正常的地址
for
(String forwardIp : forwardIpArray){
if
(!isIlegalHost(forwardIp)){
return
forwardIp;
}
}
}
//2.3 X-Forwarded-For不存在正常地址,則取遠程地址
realIp = request.getRemoteAddr();
// 防止Nginx和web同一台無法獲取地址
if
(
"127.0.0.1"
.equals(realIp)) {
try
{
// 2.4 如果是本機,則嘗試獲取網卡配置的IP
realIp = InetAddress.getLocalHost().getHostAddress();
}
catch
(UnknownHostException e) {}
}
}
return
realIp;
}
public
boolean
isIlegalHost(String ip){
if
(ip ==
null
|| ip.length() ==
0
||
"unknown"
.equalsIgnoreCase(ip)) {
return
true
;
}
return
false
;
}
-
2.場景分析
-
-
場景一:Nginx->Web;F5->Web
- F5只需界面操作,配置源地址寫入:X-Real-IP
-
Nginx配置
server {
# 監聽端口
listen 7777;
server_name localhost;
charset utf-8;
location /{
# 轉發地址
proxy_pass http://10.19.205.58:7777;
proxy_set_header Host $host;
# 源地址放入X-Real-Ip
proxy_set_header X-Real-IP $remote_addr;
# 調用地址增加到X-Forwarded-For字段
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
-
實例結果:
# 訪問路徑:http:
//10.19.205.60:7777/express
# 客戶端地址:
10.20
.
23.50
# Nginx地址:
10.19
.
205.60
# web應用地址:
10.19
.
205.58
# 代碼:request.getRemoteAddr()+
"|"
+request.getHeader(
"X-Real-IP"
)+
"|"
+ request.getHeader(
"X-Forwarded-For"
)+
"|"
+getRealIp();
# 返回結果:Remote Addr:
10.19
.
205.60
X-Real-IP:
10.20
.
23.50
X-Forwarded-For:
10.20
.
23.50
getRealIp():
10.20
.
23.50
- 異常情況分析
-
異常情況:直接調用web地址,解決方法:java示例代碼行注釋2
# 返回結果:Remote Addr:10.20.23.50
X-Real-IP:null
X-Forwarded-For:null
getRealIp():10.20.23.50
-
-
場景二:F5->Nginx->Web
-
F5只需界面操作,配置源地址寫入:X-Real-IP
- Nginx配置:
server {
# 監聽端口
listen 7777;
server_name localhost;
charset utf-8;
location /{
# 轉發web應用地址
proxy_pass http://10.19.205.58:7777;
proxy_set_header Host $host;
# F5調用地址增加到X-Forwarded-For字段
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
- 實例結果:
# 訪問路徑:http:
//10.19.18.51:7777/express
# 客戶端地址:
10.20
.
23.50
# F5虛擬地址:
10.19
.
18.51
# Nginx地址:
10.19
.
205.59
# web應用地址:
10.19
.
205.58
# 代碼:request.getRemoteAddr()+
"|"
+request.getHeader(
"X-Real-IP"
)+
"|"
+ request.getHeader(
"X-Forwarded-For"
)+
"|"
+getRealIp();
# 返回結果:Remote Addr:
10.19
.
205.59
X-Real-IP:
10.20
.
23.50
X-Forwarded-For:
10.19
.
18.254
(F5實際地址)
getRealIp():
10.20
.
23.50
-
異常情況分析
-
異常情況一:直接調用nginx地址,解決方案:java示例代碼行注釋2
# 返回結果:Remote Addr:
10.19
.
205.59
X-Real-IP:
null
X-Forwarded-For:
10.20
.
23.50
getRealIp():
10.20
.
23.50
-
異常情況二:直接調用web地址,解決方法:java示例代碼行注釋2.3
# 返回結果:Remote Addr:
10.20
.
23.50
X-Real-IP:
null
X-Forwarded-For:
null
getRealIp():
10.20
.
23.50
-
-
-
Nginx->Nginx->Web
-
第一層 Nginx配置:
server {
# 監聽端口
listen
7777
;
server_name localhost;
charset utf-
8
;
location /{
# 轉發web應用地址
proxy_pass http:
//10.19.205.58:7777;
proxy_set_header Host $host;
# 源地址放入X-Real-Ip
proxy_set_header X-Real-IP $remote_addr;
# 調用地址增加到X-Forwarded-For字段
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
-
第二層 Nginx配置:
server {
# 監聽端口
listen
7777
;
server_name localhost;
charset utf-
8
;
location /{
# 轉發web應用地址
proxy_pass http:
//10.19.205.58:7777;
proxy_set_header Host $host;
# 第一層調用地址增加到X-Forwarded-For字段
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
-
實例結果:
# 訪問路徑:http:
//10.19.205.69:7777/express
# 客戶端地址:
10.20
.
23.50
# 第一層Nginx地址:
10.19
.
205.69
# 第二層Nginx地址:
10.19
.
205.59
# web應用地址:
10.19
.
205.58
# 代碼:request.getRemoteAddr()+
"|"
+request.getHeader(
"X-Real-IP"
)+
"|"
+ request.getHeader(
"X-Forwarded-For"
)+
"|"
+getRealIp();
# 返回結果:Remote Addr:
10.19
.
205.59
X-Real-IP:
10.20
.
23.50
X-Forwarded-For:
10.20
.
23.50
,
10.19
.
205.69
getRealIp():
10.20
.
23.50
- 異常情況分析
-
異常情況一:直接調用第二層nginx地址,解決方案:java示例代碼行注釋2
# 返回結果:Remote Addr:
10.19
.
205.59
X-Real-IP:
null
X-Forwarded-For:
10.20
.
23.50
getRealIp():
10.20
.
23.50
-
異常情況二:直接調用web地址,解決方法:java示例代碼行注釋2.3
# 返回結果:Remote Addr:
10.20
.
23.50
X-Real-IP:
null
X-Forwarded-For:
null
getRealIp():
10.20
.
23.50
-
-
-