背景
如果服务器前面没有网关或者nginx等反向代理,我们可以用下面的代码获取用户的真实IP。
InetSocketAddress address = request.getRemoteSocketAddress();
String ip = address.getAddress().getHostAddress();
当使用反向代理服务器时,Web服务器将无法直接获取到用户的真实IP地址,而只能看到反向代理服务器的IP地址。这是因为反向代理服务器作为中间人,将请求代理给了Web服务器,因此Web服务器只能看到代理服务器的IP地址。
+----------+
| Client | 192.168.0.100
+----------+
|
| Request
v
+----------+
| Nginx | 10.0.0.1
+----------+
|
| Proxy Request
v
+----------+
| Web/App | 问题
| Server | request.getRemoteSocketAddress = 10.0.0.1
+----------+
Nginx 配置
为了能获取到用户的真实IP,我们可以在Nginx做如下配置:
http {
include mime.types;
default_type application/octet-stream;
server {
listen 8080;
server_name www.laker.com:8080 localhost;
#防XSS攻擊
add_header X-Xss-Protection 1;
location / {
# WebSocket Header
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection "Upgrade";
# 将客户端的 Host 和 IP 信息一并转发到对应节点
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
# 客户端与服务端无交互 60s 后自动断开连接,请根据实际业务场景设置
proxy_read_timeout 300s;
proxy_send_timeout 600;
proxy_connect_timeout 60;
# 执行代理访问真实服务器
proxy_pass http://192.1.2.11:8085;
}
}
}
相关请求头部介绍
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# “Via”头部设置了代理服务器的名称,这里设置为“nginx”
proxy_set_header Via "nginx";
上面的header key X-Real-IP,X-Forwarded-For其实可以随便取名称,但是一般都是按照这个名称为规范。
-
Host
包含客户端真实的域名和端口号; -
X-Forwarded-Proto
表示客户端真实的协议(http还是https); -
X-Real-IP
表示客户端真实的IP;建议用这个配置来获取用户的真实IP。
多层反向代理:
$remote_addr
只能获取到与服务器本身直连的上层请求ip,所以设置$remote_addr一般都是设置第一个代理
上面;
X-Forwarded-For
这个Header和X-Real-IP
类似,但它在多层代理时会包含真实客户端及中间每个代理服务器的IP。
$proxy_add_x_forwarded_for
变量包含客户端请求头中的X-Forwarded-For
与$remote_addr
两部分,他们之间用逗号分开.多层反向代理结果如下:
X-Forwarded-For: 来访者真实IP, 代理服务器1-IP, 代理服务器2-IP, ...
X-Forwarded-For: 192.168.0.100, 10.0.0.1, 172.16.0.1
当使用此方式获取来访者真实IP时,获取的第一个地址就是来访者真实IP。
这个配置可以用于获取用户真实IP,但是更好的用于得知经过几层代理服务器。
需要
每个反向代理
服务器都配置这个请求头。为了安全一般采用如下配置:
第一层反向代理:
proxy_set_header X-Real-PORT $remote_port; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; // 重点
第二层反向代理:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; // 重点
如果在第一层也用
$proxy_add_x_forwarded_for
去获取,可能获取的是不可信的,因为用户可以修改header。
这样就可以达到如下效果:
+----------+
| Client | 192.168.0.100
+----------+
|
| Request
v
+----------+
| Nginx | 10.0.0.1
+----------+
|
| Proxy Request
v
+----------+
| Web/App | request.getRemoteSocketAddress = 10.0.0.1 这个是无法更改的。
| Server | 改用 request.getHeader("X-Real-IP") 获取。
+----------+
HTTP Headers Example
-------------------
X-Forwarded-For: 192.168.0.100, 10.0.0.1
X-Real-IP: 192.168.0.100
Via: nginx
示例代码
/***
* 获取客户端IP地址;这里通过了Nginx获取;X-Real-IP
*/
public static String getClientIP(HttpServletRequest request) {
String fromSource = "X-Real-IP";
String ip = request.getHeader("X-Real-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
fromSource = "X-Forwarded-For";
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
fromSource = "Proxy-Client-IP";
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
fromSource = "WL-Proxy-Client-IP";
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
fromSource = "request.getRemoteAddr";
}
return ip;
}