Nginx realip模块之 $remote_addr 和 X-Forwarded-For

原创
2021/04/06 14:42
阅读数 3.2K

一个请求肯定是可以分为请求头和请求体的,而我们客户端的IP地址信息一般都是存储在请求头里的。如果你的服务器有用Nginx做负载均衡的话,你需要在你的location里面配置X-Real-IPX-Forwarded-For请求头:

    location ^~ /your-service/ {
            proxy_set_header        X-Real-IP       $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://localhost:60000/your-service/;
    }

关于X-Real-IP

经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,通过$remote_addr变量拿到的将是反向代理服务器的ip地址。

——《实战nginx》

这句话的意思是说,当你使用了nginx反向服务器后,在web端使用request.getRemoteAddr()(本质上就是获取$remote_addr),取得的是nginx的地址,即$remote_addr变量中封装的是nginx的地址,当然是没法获得用户的真实ip的。但是,nginx是可以获得用户的真实ip的,也就是说nginx使用$remote_addr变量时获得的是用户的真实ip,如果我们想要在web端获得用户的真实ip,就必须在nginx里作一个赋值操作,即我在上面的配置:

proxy_set_header X-Real-IP $remote_addr;

$remote_addr 只能获取到与服务器本身直连的上层请求ip,所以设置$remote_addr一般都是设置第一个代理上面;但是问题是,有时候是通过cdn访问过来的,那么后面web服务器获取到的,永远都是cdn 的ip 而非真是用户ip,那么这个时候就要用到X-Forwarded-For 了,这个变量的意思,其实就像是链路反追踪,从客户的真实ip为起点,穿过多层级的proxy ,最终到达web 服务器,都会记录下来,所以在获取用户真实ip的时候,一般就可以设置成,proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 这样就能获取所有的代理ip 客户ip。

关于X-Forwarded-For

X-Forwarded-For变量,这是一个squid开发的,用于识别通过HTTP代理或负载平衡器原始IP一个连接到Web服务器的客户机地址的非rfc标准,如果有做X-Forwarded-For设置的话,每次经过proxy转发都会有记录,格式就是client1,proxy1,proxy2以逗号隔开各个地址,由于它是非rfc标准,所以默认是没有的,需要强制添加。在默认情况下经过proxy转发的请求,在后端看来远程地址都是proxy端的ip 。也就是说在默认情况下我们使用request.getAttribute("X-Forwarded-For")获取不到用户的ip,如果我们想要通过这个变量获得用户的ip,我们需要自己在nginx添加配置:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

意思是增加一个$proxy_add_x_forwarded_forX-Forwarded-For里去,注意是增加,而不是覆盖,当然由于默认的X-Forwarded-For值是空的,所以我们总感觉X-Forwarded-For的值就等于$proxy_add_x_forwarded_for的值,实际上当你搭建两台nginx在不同的ip上,并且都使用了这段配置,那你会发现在web服务器端通过request.getAttribute("X-Forwarded-For")获得的将会是客户端ip和第一台nginx的ip。

那么$proxy_add_x_forwarded_for又是什么?

$proxy_add_x_forwarded_for变量包含客户端请求头中的X-Forwarded-For$remote_addr两部分,他们之间用逗号分开。

举个例子,有一个web应用,在它之前通过了两个nginx转发,www.linuxidc.com即用户访问该web通过两台nginx。

在第一台nginx中,使用:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

现在的$proxy_add_x_forwarded_for变量的X-Forwarded-For部分是空的,所以只有$remote_addr,而$remote_addr的值是用户的ip,于是赋值以后,X-Forwarded-For变量的值就是用户的真实的ip地址了。

到了第二台nginx,使用:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

现在的$proxy_add_x_forwarded_for变量,X-Forwarded-For部分包含的是用户的真实ip,$remote_addr部分的值是上一台nginx的ip地址,于是通过这个赋值以后现在的X-Forwarded-For的值就变成了“用户的真实ip,第一台nginx的ip”,这样就清楚了吧。

realip模块

realip模块的作用是:当本机的nginx处于一个反向代理的后端时获取到真实的用户IP。

如果没有realip模块,nginx的access_log里记录的IP会是反向代理服务器的IP,PHP中$_SERVER[‘REMOTE_ADDR’]的值也是反向代理的IP。

而安装了realip模块,并且配置正确,就可以让nginx日志和php的REMOTE_ADDR都变成真实的用户IP。

举一个最简单的例子:

如图使用slb做反向代理服务器对外提供服务,后端使用nginx提供web服务:

  • 如果不使用realip模块,nginx中拿到的客户端ip是slb的服务器ip地址100.122.59.106,即 $remote_addr 是 100.122.59.106。

  • 如果使用realip模块,配置内容如下,此时nginx中拿到的客户端ip是用户真实ip地址124.240.3.71,即$remote_addr 是 124.240.3.71。

    set_real_ip_from 100.122.0.0/10; 
    real_ip_header X-Forwarded-For;
    

set_real_ip_from 指令的作用是告诉nginx,100.122.0.0/10 这个ip段是我们的反代服务器(信任服务器),不是真实的用户IP,real_ip_heaer 则是告诉nginx真正的用户IP是存在X-Forwarded-For 请求头中。

使用realip模块并重新加载nginx配置之后,就可以看到nginx日志里记录的$remote_addr就是124.240.3.71 了,php里的REMOTE_ADDR也是124.240.3.71。

了解:

realip模块还提供了另外一个指令real_ip_recursive,可以用来处理更加复杂的情况。

首先要明确一点,realip模块生效的前提是:直接连接nginx的ip是在set_real_ip_from中指定的。

当real_ip_recursive为off时,nginx会把real_ip_header指定的HTTP头中的最后一个IP当成真实IP。

当real_ip_recursive为on时,nginx会把real_ip_header指定的HTTP头中的最后一个不是信任服务器的IP当成真实IP。

$remote_addr 和 X-Forwarded-For应用场景

某服务采用的是LNMP架构,如下图:

现在需求如下:

  1. 部分内部接口禁止外部直接调用,且内部调用时无需接口校验,安全起见,需要设置访问白名单;
  2. 获取用户地理位置。

实现方法:

  • 不使用realip模块,此时PHP中使用 $_SERVER['REMOTE_ADDR'] 拿到的 $remote_addr 是SLB服务器的ip,直接把 $remote_addr 作为ip白名单;
  • 定位功能需要获取用户真实ip,因为没使用realip模块,此时无法通过 $remote_addr 获取用户真实ip,那么怎么办?答案是:在PHP中通过$_SERVER['HTTP_X_FORWARDED_FOR']来获取realip。
  • 如此一来互不冲突。

至于nginx日志中记录哪些信息,为了定位问题方便,日志格式为:

log_format main '$remote_addr - [$time_local] $scheme "$request" $status $upstream_status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" ["$request_time", "$upstream_response_time"] $http_x_forwarded_for "$http_cookie"';
展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部