2025-03-26    2025-03-30    2432 字  5 分钟
web

点解此处查看视频教程

当你的网站使用多层Nginx代理(如CDN + 负载均衡 + 后端服务器),客户端真实IP总是被覆盖?日志里全是代理服务器的IP?我们应该如何在这种情况下透传客户端真实IP呢?

环境

网络架构图

网络架构图

proxy-3上准备后端程序并启动,后端服务将监听在8080端口。

1
2
3
wget -O /opt/http_server https://tools.snoopyops.top/file/http_server
chmod a+x /opt/http_server
/opt/http_server

所有服务器Nginx日志格式均为默认格式。

1
2
3
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for"';

remote_addr

Nginx日志中第一个字段就表示remote_addr,先对Nginx不做多余配置,我们观察获取到的remote_addr地址。

配置Proxy_3,将来自test.snoopyops.top的所有请求都转发到本机的8080端口。

1
2
3
4
5
6
7
8
9
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.31.36.28:8080;
		proxy_set_header Host $http_host;
	}
}

配置Proxy_2,将来自test.snoopyops.top的所有请求都转发到Proxy_3上。

1
2
3
4
5
6
7
8
9
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.31.36.28;
		proxy_set_header Host $http_host;
	}
}

配置Proxy_1,同理将来自test.snoopyops.top的所有请求都转发到Proxy_2上。

1
2
3
4
5
6
7
8
9
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.30.144.202;
		proxy_set_header Host $http_host;
	}
}

做完域名解析后,我们访问test.snoopyops.top后,依次查看所有服务器的Nginx日志。

Proxy_1Nginx日志中,我们通过remote_addr获取到了客户端的真实IP

1
219.143.190.117 - - [03/Mar/2025:11:36:35 +0800] "GET / HTTP/1.1" 200 52 "-" "curl/8.4.0" "-"

Proxy_2Nginx日志中,我们通过remote_addr获取到的是Proxy_1IP

1
172.18.6.10 - - [03/Mar/2025:11:36:35 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "-"

Proxy_3Nginx日志中,我们通过remote_addr获取到的是Proxy_2IP

1
172.30.144.202 - - [03/Mar/2025:11:36:35 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "-"

由此我们可以总结出:remote_addr这个变量表示的是与Nginx服务器直接建立TCP连接的客户端的IP地址。客户端是直接与Proxy_1建立连接的,所以Proxy_1可以通过remote_addr获取到客户端真实IP,而与Proxy_2建立连接的是Proxy_1,所以他就只能记录Proxy_1的IP地址了。

在没有经过多层代理的情况下,我们可以通过remote_addr获取客户端真实IP

X-Real-IP

既然我们在Proxy_1上可以通过remote_addr获取到客户端的真实IP,那么我们可以在Proxy_1上定义一个X-Real-IP的请求头,将remote_addr赋值给X-Real-IP。并将X-Real-IP一直传递到后端服务,这样后端服务就可以通过获取X-Real-IP的值达到获取客户端的真实IP的目的了。

Proxy_1上,我们做如下配置,将获取到的remote_addr的值赋值给X-Real-IP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.30.144.202;
		proxy_set_header Host $http_host;
		# 添加请求X-Real-IP头,并将remote_addr赋值给X-Real-IP
		proxy_set_header X-Real-IP $remote_addr;
	}
}

我们在Proxy_2中,获取proxy_1定义的X-Real-IP,并赋值给自己的给自己定义的X-Real-IP请求头。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.31.36.28;
		proxy_set_header Host $http_host;
		proxy_set_header X-Real-IP $http_x_real_ip;
	}
}

同理,我们在Proxy_3上也定义一个X-Real-IP,并将获取到的上一级的X-Real-IP赋值给自己的X-Real-IP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.31.36.28:8080;
		proxy_set_header Host $http_host;
		proxy_set_header X-Real-IP $http_x_real_ip;
	}
}

由于X-Real-IP为自定义字段,Nginx日志没有打印此字段,所以Nginx日志没有发生变化。

Proxy_1Nginx日志如下:

1
219.143.190.117 - - [03/Mar/2025:11:47:28 +0800] "GET / HTTP/1.1" 200 52 "-" "curl/8.4.0" "-"

Proxy_2Nginx日志如下:

1
172.18.6.10 - - [03/Mar/2025:11:47:28 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "-"

Proxy_3Nginx日志如下:

1
172.30.144.202 - - [03/Mar/2025:11:47:28 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "-"

但是我们后端服务已经可以通过X-Real-IP获取到客户端的真实IP了。服务端日志如下:

1
2
Server Started on :8080
Client IP: 219.143.190.117 | Method: GET | URL: /

X-Real-IP是一个自定义的HTTP头部字段,通常用于在代理服务器转发请求时,传递客户端的真实IP地址。当使用代理时,代理服务器会将客户端的真实IP地址填充到这个头部字段中,然后后端服务可以通过配置从这个头部获取客户端真实IP。不过,它并非标准HTTP头部,依赖于代理服务器是否正确设置,且如果代理链中有恶意节点,可能会被篡改。

X-Forwarded-For

X-Forwarded-For是一个标准的HTTP头部字段,用于记录请求经过的代理服务器列表。它的格式为client_ip, proxy1_ip, proxy2_ip...,其中client_ip是客户端的真实IP,后面依次是请求经过的代理服务器IP。 相比X-Real-IPX-Forwarded-For更规范,被广泛支持。但由于它记录了整个代理链的IP,在处理时需要解析地址,获取第一个字段。

我们需要在Nginx服务器上配置X-Forwarded-For请求头。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# proxy_1的配置
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.30.144.202;
		proxy_set_header Host $http_host;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	}
}

# proxy_2的配置
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.31.36.28;
		proxy_set_header Host $http_host;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	}
}

# proxy_3的配置
server {
	listen 80;
	server_name test.snoopyops.top;

	location / {
		proxy_pass http://172.31.36.28:8080;
		proxy_set_header Host $http_host;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	}
}

Nginx默认的日志格式中,最后一个字段表示的就是X-Forwarded-For,各级代理日志如下,我们发现客户端真实IP及各级代理IP都会被记录。

1
2
3
4
5
6
7
8
# Proxy_1的Nginx日志
219.143.190.117 - - [03/Mar/2025:11:55:35 +0800] "GET / HTTP/1.1" 200 52 "-" "curl/8.4.0" "-"

# Proxy_2的Nginx日志
172.18.6.10 - - [03/Mar/2025:11:55:35 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "219.143.190.117"

# Proxy_3的Nginx日志
172.30.144.202 - - [03/Mar/2025:11:55:35 +0800] "GET / HTTP/1.0" 200 52 "-" "curl/8.4.0" "219.143.190.117, 172.18.6.10"

总结

字段 说明
remote_addr 表示的是与Nginx服务器直接建立TCP连接的客户端的IP地址,多级代理下无法获取客户端真实IP。
X-Real-IP 自定义的HTTP头部字段,可传递客户端的真实IP地址,依赖于代理服务器是否正确设置,有被篡改的风险。
X-Forwarded-For 标准的HTTP头部字段,用于记录请求经过的代理服务器列表,但由于它记录了整个代理链的IP。在处理时需要解析地址,获取第一个字段。