共计 3724 个字符,预计需要花费 10 分钟才能阅读完成。
背景
后端业务往往不期望关心细节,只期望能从上游 nginx 请求中获取到准确的真实IP,帮助进行更精准的日志分析和访问控制。
在实际的请求链路中,流量往往会经过多层代理。例如 用户 –> proxy1 –> proxy2 –>proxy* –> service。
不当的配置,往往会导致没能正确获取用户的真实IP。例如可能从 proxy2 中只获取到了 proxy1 的IP。
本文会聊聊 nginx 如何实现准确获取用户真实IP。
术语
在 nginx 中,有几个术语和 IP 相关:
-
Remote Address
- Remote Address 是 Nginx 在处理请求时直接获取到的客户端 IP 地址。
- 它是 Nginx 内部的一个变量,可以在 Nginx 配置文件中直接使用 $remote_addr 来引用。
-
X-Real-IP
- X-Real-IP 是一个 自定义的HTTP 请求头字段。目前并不属于任何标准,代理和 Web 应用之间可以约定用任何自定义头来传递信息。
- 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP,这个设备可能是其他代理,也可能是真正的请求端。
- 在 Nginx 配置文件中,可以使用 $http_x_real_ip 变量来获取 X-Real-IP 的值。
-
X-Forwarded-For:
- X-Forwarded-For 也是一个 HTTP 请求头字段。
- 当客户端的请求经过多层代理时,每个代理服务器都会在 X-Forwarded-For 头部追加客户端的 IP 地址。
- 在 Nginx 配置文件中,可以使用 $http_x_forwarded_for 变量来获取 X-Forwarded-For 的值。
实践
若链路简单,例如:用户 –> proxy1 –> service。这样的话在 nginx 中 利用上述三种方式,其实都可以获取到用户真实IP。
下文以此图为示例,分析如何在每层代理都可以获取到用户真实IP:
若在 三台 proxy 上均使用 $remote_addr 获取IP,日志的IP会是:
[proxy1] 7.7.7.7
[proxy2] 192.168.2.1
[proxy3] 192.168.2.2
即 $remote_addr
只能获取到上一跳设备的真实IP。
在此案例中,下游的 proxy 已经无法获取上游客户端的真实IP。
若在 三台 proxy 上均使用 $http_x_forwarded_for 获取IP,日志的IP会是:
[proxy1] 7.7.7.7
[proxy2] 7.7.7.7, 192.168.2.1
[proxy3] 7.7.7.7, 192.168.2.1, 192.168.2.2
即每层代理都会在 $http_x_forwarded_for
追加上游的 IP。此时下游无法直接获取用户真实IP。当然后端服务可以获取 XFF 的第一个IP用于判断,不过我们尽量在代理层直接处理好细节最佳。
remote_addr 传递给 http_x_real_ip
由于 X-Real-IP
是一个 自定义的HTTP 请求头字段,它的值我们可以自行定义。
由于 proxy 1
中的 $remote_addr
可以获取到用户真实IP,那么我们可以将它传递给 $http_x_real_ip
。
这样就可以用 $http_x_real_ip
来准确获取用户真实IP了。
此时 proxy 1
nginx 的配置范例为:
http {
# 保留真实IP
proxy_set_header X-Real-IP $remote_addr; #将 $remote_addr 传递给 X-Real-IP
# 在日志中使用 $http_x_real_ip 保留客户端真实IP
log_format main '$http_x_real_ip - $scheme [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$host" "$upstream_addr" "$upstream_cache_status" $request_time $upstream_response_time';
access_log /data/logs/nginx/access.log main;
}
由于 proxy 1
已经将真实IP传递给了 $http_x_real_ip
,在 proxy 2
和 proxy 3
中,不需要再进行传递即可直接获取。
即 proxy 2
、proxy 3
配置无需添加 proxy_set_header X-Real-IP $remote_addr;
此时后端服务可以从 $http_x_real_ip
获取到真实IP。
从 http_x_forwarded_for 检索真实IP 传递给 remote_addr
上文已经说到, $http_x_forwarded_for
可以详细记录整个链路中出现的所有IP,且第一个IP即为用户的真实IP。
那么我们可以利用 nginx 的 ngx_http_realip_module
模块,将 $http_x_forwarded_for
中的真实IP进行提取,并传递给 $remote_addr
。
由于 proxy 1
的 $remote_addr
已经为真实IP,所以我们只需要在 proxy 3
中进行配置检索,即可将用户IP传递给后端服务。proxy 2
可配可不配,取决于自己需求。
但 proxy 1
和 proxy 2
、proxy 3
都需要配置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
用于传递。
在 X-Forwarded-For
头部字段中, IP 地址的添加顺序是这样的:
- 当客户端第一次访问时,
X-Forwarded-For
头部为空。 - 当请求经过第一个代理服务器时,
X-Forwarded-For
头部会被设置为代理服务器的 IP 地址。 - 当请求经过第二个代理服务器时,
X-Forwarded-For
头部会被设置为 “代理服务器2 IP, 代理服务器1 IP”。 - 以此类推,
X-Forwarded-For
头部会将经过的每个代理服务器的 IP 地址依次添加到最前面。
此时 proxy 3
nginx 的配置范例为:
http {
# 保留真实IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 在日志中使用 $http_x_real_ip 保留客户端真实IP
log_format main '$http_x_real_ip - $scheme [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$host" "$upstream_addr" "$upstream_cache_status" $request_time $upstream_response_time';
access_log /data/logs/nginx/access.log main;
server {
...
location / {
set_real_ip_from *.*.*.*/*;
real_ip_header X-Forwarded-For;
real_ip_recursive of;
...
}
}
}
-
set_real_ip_from *.*.*.*/*;
- 这个指令用于指定一个或多个 IP 地址或 CIDR 块,这些地址或网段中的客户端 IP 地址将被视为真实的客户端 IP 地址。
- 通常情况下,当 Nginx 位于负载均衡器或代理服务器之后时,客户端的真实 IP 地址会被这些中间设备覆盖掉,变成了中间设备的 IP 地址。这个指令就是用来告诉 Nginx 哪些 IP 地址或网段是可信的,Nginx 应该将这些地址视为客户端的真实 IP 地址。
-
real_ip_header X-Forwarded-For;
- 这个指令用于指定从哪个 HTTP 头部字段中获取客户端的真实 IP 地址。
- 在负载均衡或代理的场景下,客户端的真实 IP 地址通常会被写入
X-Forwarded-For
头部字段。这个指令就是告诉 Nginx 从这个头部字段中获取客户端的真实 IP 地址。
-
real_ip_recursive;
- 这个指令用于控制 Nginx 在解析
X-Forwarded-For
头部字段时的行为。 - 当设置为
on
时,Nginx 会递归地解析X-Forwarded-For
头部字段,取最后一个非私有 IP 地址作为客户端的真实 IP 地址,传递给$remote_addr
。 - 当设置为
off
时,Nginx 会直接使用X-Forwarded-For
头部字段中的第一个 IP 地址作为客户端的真实 IP 地址,传递给$remote_addr
。
- 这个指令用于控制 Nginx 在解析
此时后端服务可以从 $remote_addr
获取到真实IP。
总结
文章中描述的是通常情况下对于真实IP的获取方法。
若链路中有恶意伪造 X-Forwarded-For
请求头的现象时,我们尽量配置好 set_real_ip_from
限定可信的IP源,可以在一定程度上防止 $remote_addr
被篡改。
本文属于专题:Nginx
- Nginx 准确获取真实IP
- Nginx 获取真实IP进行访问控制
- Nginx 日志分析之 Loki