nginx 是通过 ngx_http_realip_module 模块来实现获取客户端真实 IP 的;
一般一个请求在网络传输时会经过多个代理层,当请求到达真实应用服务器时如何获取客户端真实 IP 就是一个问题了;
以下是介绍 nginx 如何处理来获取到客户端真实 IP,及 nginx 同时作为反向代理时如何传递客户端真实 IP 给应用服务器;
大家可以先自行网络上了解下 X-Forwarded-For(XFF),这里推荐一篇文章 「链接」
个人理解如下:
假如客户端 C 访问应用服务器 S 中间经过三层代理,分别是 Proxy1 / Proxy2 / Proxy3,X-Forwarded-For 处理流程如下:
客户端 C 访问 Proxy1,Proxy1 会记录客户端地址 IP0,Proxy1 把请求转发给 Proxy2 的时候会在请求头部加上 X-Forwarded-For : IP0;Proxy2 接收到 Proxy1 的请求后也会记录 Proxy1 的地址 IP1,发现请求头中有头部字段 X-Forwarded-For : IP0,Proxy2 把请求转发给 Proxy3 的时候会拼接 Proxy1 的地址,即 X-Forwarded-For : IP0, IP1;同理,Proxy3 接收到 Proxy2 的请求后,把请求转发给应用服务器的时候会拼接 Proxy2 的地址,即 X-Forwarded-For : IP0, IP1, IP2;应用服务器接收到 Proxy3 的请求,头部字段X-Forwarded-For : IP0, IP1, IP2,没有 Proxy3 的IP地址,nginx 可以通过 $remote_addr 变量获取,web 应用服务可以通过 request.getRemoteAddr() 方法获取;目录
模块指令
ngx_http_realip_module 模块有如下三个指令;
1、set_real_ip_from
该指令用于设置授信 IP,即请求过来时由某个头字段携带的 IP 中 nginx 自己认为可信的 IP,该头字段由 real_ip_header 指令指定;该指令值一般是前几层代理的 IP ;2、real_ip_header
该指令用于告知 nginx 从每个客户端请求中的哪个头字段来获取客户端真实的 IP;该指令默认值是 X-Real-IP,不过现在主流的都是通过 X-Forwarded-For 字段来获取客户端真实 IP,X-Forwarded-For 目前已经是主流运用的字段了;我们也可以在 nginx 配置时自定义一个新的字段;3、real_ip_recursive
nginx 从 real_ip_header 指令指定的头字段中获取 IP,可能会有多个 IP 值;
当 real_ip_recursive 指令值为 off,nginx 从获取到 IP 值中从右往左(也即从后往前)的顺序,以最后一个 IP 值作为客户端的真实 IP ,此时不会排除授信 IP;当 real_ip_recursive 指令值为 on,nginx 从获取到 IP 值中从右往左(也即从后往前)的顺序,排除 set_real_ip_from 指令指定的授信 IP,以最后一个非授信 IP 值作为客户端的真实 IP ;实验
【实验一】
客户端真实 IP 192.168.135.1,我们设置两个头字段,分别如下
X-Forwarded-For192.168.135.12, 192.168.135.22 mfz192.168.135.13, 192.168.135.23nginx 不做任何相关的 IP 指令配置,设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; } }我们从 nginx 的日志文件 access.log 中看效果,默认日志格式如下
发现 nginx 拿到客户端的地址是 192.168.135.1 ,即 $remote_addr 变量值;
【实验二】
客户端真实 IP 192.168.135.1,我们设置两个头字段,分别如下
X-Forwarded-For192.168.135.12, 192.168.135.22 mfz192.168.135.13, 192.168.135.23设置 real_ip_header X-Forwarded-For,如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; real_ip_header X-Forwarded-For; } }我们从 nginx 的日志文件 access.log 中看效果
发现 nginx 拿到客户端的地址还是 192.168.135.1 ,即 $remote_addr 变量值;nginx 是通过 TCP 连接拿到最近一层代理的 IP 并赋值给 $remote_addr 变量,因为请求是由代理服务器转发过来的,是上一层代理服务和 nginx 服务产生了 TCP 连接;如果客户端和 nginx 服务器之间没有代理层,那么 $remote_addr 就是客户端的地址,因为客户端直接和 nginx 服务进行 TCP 连接;
【实验三】
客户端真实 IP 192.168.135.1,我们设置两个头字段,分别如下
X-Forwarded-For192.168.135.12, 192.168.135.22 mfz192.168.135.13, 192.168.135.23把客户端真实 IP 作为授信 IP,设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; real_ip_header X-Forwarded-For; set_real_ip_from 192.168.135.1; } }我们从 nginx 的日志文件 access.log 中看效果
发现 nginx 拿到客户端的地址是 192.168.135.22 ,即 X-Forwarded-For 头字段的最后一个 IP;
也就是说变量 $remote_addr 的值变成 192.168.135.22 了,此时 set_real_ip_from 指令管用,$remote_addr 值会排除掉授信 IP,拿到 real_ip_header 指令指定的头字段携带的 IP最后一个作为真实的客户端IP,赋值给变量 $remote_addr;
【实验四】
客户端真实 IP 192.168.135.1,我们去掉 X-Forwarded-For 字段,只设置一个头字段,分别如下
mfz192.168.135.13, 192.168.135.23把客户端真实 IP 作为授信 IP,设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; real_ip_header X-Forwarded-For; set_real_ip_from 192.168.135.1; } }我们从 nginx 的日志文件 access.log 中看效果
发现 nginx 拿到客户端的地址变为 192.168.135.1 了,是客户端真实 IP;
也就是说变量 $remote_addr 的值变成 192.168.135.1 了,此时 set_real_ip_from 指令不管用了,因为过来的请求头中没有 real_ip_header 指定的字段;
【实验五、在实验四的基础上】
客户端真实 IP 192.168.135.1,我们去掉 X-Forwarded-For 字段,只设置一个头字段,分别如下
mfz192.168.135.13, 192.168.135.23把客户端真实 IP 作为授信 IP,real_ip_header 指令值字段自定义,设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; real_ip_header mfz; set_real_ip_from 192.168.135.1; } }我们从 nginx 的日志文件 access.log 中看效果
发现 nginx 拿到客户端的地址是 192.168.135.23 ,即 mfz 头字段的最后一个 IP;
也就是说变量 $remote_addr 的值变成 192.168.135.23 了,此时 set_real_ip_from 指令又管用了,因为过来的请求头中包含 real_ip_header 指令指定的字段,然后会从该头字段中拿最后一个 IP 作为客户端真实 IP;
【实验六、在实验五的基础上】
客户端真实 IP 192.168.135.1,我们去掉 X-Forwarded-For 字段,只设置一个头字段,分别如下
mfz192.168.135.13, 192.168.135.23把客户端真实 IP 作为授信 IP,real_ip_header 指令值字段自定义,设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; real_ip_header mfz; set_real_ip_from 192.168.135.1; } }重新配置日志格式把 $proxy_add_x_forwarded_for 变量配置进去
我们从 nginx 的日志文件 access.log 中看效果
$remote_addr 192.168.135.23 ,$proxy_add_x_forwarded_for 192.168.135.23 ;
变量 $remote_addr 与变量 $proxy_add_x_forwarded_for 的关系如下图,此时 set_real_ip_from 指令管用;
【实验七、在实验六的基础上】
客户端真实 IP 192.168.135.1,我们设置两个头字段,分别如下
X-Forwarded-For 192.168.135.12, 192.168.135.22 mfz192.168.135.13, 192.168.135.23把客户端真实 IP 作为授信 IP,real_ip_header 指令值字段自定义,设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; real_ip_header mfz; set_real_ip_from 192.168.135.1; } }我们从 nginx 的日志文件 access.log 中看效果
发现变量值如下,
$remote_addr 192.168.135.23 ,$proxy_add_x_forwarded_for 192.168.135.12, 192.168.135.22, 192.168.135.23 ;
此时 set_real_ip_from 指令管用,real_ip_header 指令指定的头字段携带的 IP 最后一个被 nginx 认为是客户端真实的 IP;
$proxy_add_x_forwarded_for 变量的值等于请求头字段 X-Forwarded-For 的值 + $remote_addr 变量的值;
【实验八、在实验七的基础上】
客户端真实 IP 192.168.135.1,我们设置两个头字段,分别如下
X-Forwarded-For 192.168.135.12, 192.168.135.22 mfz192.168.135.13, 192.168.135.23把客户端真实 IP 作为授信 IP,设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; real_ip_header X-Forwarded-For; set_real_ip_from 192.168.135.1; } }我们从 nginx 的日志文件 access.log 中看效果
发现变量值如下,
$remote_addr 192.168.135.22 ,$proxy_add_x_forwarded_for 192.168.135.12, 192.168.135.22, 192.168.135.22 ;
【实验九、在实验八的基础上】
客户端真实 IP 192.168.135.1,我们设置两个头字段,分别如下
X-Forwarded-For 192.168.135.12, 192.168.135.22 mfz192.168.135.13, 192.168.135.23设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; set_real_ip_from 192.168.135.22; set_real_ip_from 192.168.135.23; real_ip_header X-Forwarded-For; } }我们从 nginx 的日志文件 access.log 中看效果
发现变量值如下,
$remote_addr 192.168.135.1 ,$proxy_add_x_forwarded_for 192.168.135.12, 192.168.135.22, 192.168.135.1 ;
可以发现 nginx 是优先通过 TCP 连接获取 IP 并认为此 IP 是客户端真实的 IP,即使配置 ngx_http_realip_module 模块,该模块指令也不会管用。
当通过 TCP 连接拿到 IP 被 set_real_ip_from 指令指定为授信 IP 时 nginx 才会 ngx_http_realip_module 模块的逻辑获取客户端真实 IP;
【实验十、在实验九的基础上】
客户端真实 IP 192.168.135.1,我们设置两个头字段,分别如下
X-Forwarded-For 192.168.135.12, 192.168.135.22 mfz192.168.135.13, 192.168.135.23设置如下
# Proxy upstream backend { server 192.168.135.128:8906; } server { listen 8093; server_name _; underscores_in_headers on; location /test { proxy_pass ; set_real_ip_from 192.168.135.22; set_real_ip_from 192.168.135.23; set_real_ip_from 192.168.135.1; real_ip_header X-Forwarded-For; real_ip_recursive on; } }我们从 nginx 的日志文件 access.log 中看效果
发现变量值如下,
$remote_addr 192.168.135.12 ,$proxy_add_x_forwarded_for 192.168.135.12, 192.168.135.22, 192.168.135.12 ;
此时 real_ip_recursive 指令配置为 on,当 real_ip_recursive 指令配置为 off 时变量值如下,
$remote_addr 192.168.135.22 ,$proxy_add_x_forwarded_for 192.168.135.12, 192.168.135.22, 192.168.135.22 ;