Nginx获取用户IP地址的方法
2020-02-13 17:47:34 阿炯

在实际的应用需求中,有相当多的地方需要得到用户的IP地址,可是怎么去获取用户的真实IP呢。通常有REMOTE_ADDR和X-Forwarded-For、X-Real-IP,那么它们之间的关系请见下文。

X-Real-IP是Nginx特有的,通过配置proxy_set_header X-Real-IP $remote_addr,即可从REMOTE_ADDR中取值。一般来说,X-Forwarded-For是用于记录代理信息的,每经过一级代理(匿名代理除外),代理服务器都会把这次请求的来源IP追加在X-Forwarded-For中。

HTTP reverse proxy uses non-standard headers to inform the upstream server about the user's IP address and other request properties:
X-Forwarded-For: 12.34.56.78, 23.45.67.89
X-Real-IP: 12.34.56.78
X-Forwarded-Host: example.com
X-Forwarded-Proto: https

Nginx even provides a $proxy_add_x_forwarded_for variable to automatically append $remote_addr to any incoming X-Forwarded-For headers.

RFC 7239 standardizes a new Forwarded header to carry this information in a more organized way:
Forwarded: for=12.34.56.78;host=example.com;proto=https, for=23.45.67.89


X-Forwarded-For

X-Forwarded-For 是一个 HTTP 扩展头部。HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP。如今它已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中。

X-Forwarded-For 请求头格式非常简单,就这样:
X-Forwarded-For: clientip, proxy1, proxy2

可以看到,XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP。

如果一个 HTTP 请求到达服务器之前,经过了三个代理 proxy1、proxy2、proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,那么按照 XFF 标准,服务端最终会收到以下信息:
X-Forwarded-For: IP0, IP1, IP2

proxy3 直连服务器,它会给 XFF 追加 IP2,表示它是在帮 proxy2 转发请求。列表中并没有 IP3,IP3 可以在服务端通过 Remote Address 字段获得。我们知道 HTTP 连接基于 TCP 连接,HTTP 协议中没有 IP 的概念,Remote Address 来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP,在这个例子里就是 IP3。

Remote Address 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。

在 nginx ngx_http_proxy_module的 proxy_set_header 指令中,可以通过内置变量 $proxy_add_x_forwarded_for 不断的将 $remote_addr 的值追加到 X-Forwarded-For 中。若请求头中没有 X-Forwarded-For,那么 $proxy_add_x_forwarded_for 的值和 $remote_addr 相等。


X-Forwarded-For和REMOTE_ADDR

REMOTE_ADDR代表着客户端的IP,但是这个客户端是相对服务器而言的,也就是实际上与服务器相连的机器的IP(建立tcp连接的那个),这个值是不可伪造的,如果没有代理的话,这个值就是用户实际的IP值,有代理的话,用户的请求会经过代理再到服务器,这个时候REMOTE_ADDR会被设置为代理机器的IP值。

正如前面所说,有了代理就获取不了用户的真实IP,由此X-Forwarded-For应运而生,它是一个非正式协议,在请求转发到代理的时候代理会添加一个X-Forwarded-For头,将连接它的客户端IP(也就是上网机器的公网IP)加到这个头信息里,这样末端的服务器就能获取真正上网的人的IP了。假设用户的请求顺序如下:
用户公网ip->代理服务器1–>代理服务器2–>目标服务器
REMOTE_ADDR:代理服务器2的IP值
X-Forwarded-For就是:用户公网IP,代理1的IP,代理2的IP
在这里只有REMOTE_ADDR是可信的,其他从客户端获取的数据都是不可信的,都是可伪造的。

服务器端开了nginx反向代理的时候,每次获取的都是反向代理的IP,这不是我们所希望的,需要nginx在配置反向代理的时候做一定设置并且修改。如:
proxy_set_header    Host $host;
proxy_set_header    X-Real-IP $remote_addr;
proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;

或者采用realip模块,配置如下:
set_real_ip_from   10.1.10.0/24;
real_ip_header     X-Forwarded-For;

在存在反向代理的情况下,如果直接获取REMOTE_ADDR,得到的是反向代理IP的值,从上面的配置也可以看出,在反向代理nginx的配置中将REMOTE_ADDR赋给了X-Real-IP,那么就要从X-Real-IP中来获取用户的IP。即在有反向代理时,先将REMOTE_ADDR赋给X-Real-IP,最后可以从X-Real-IP中获取用户的IP。


X-Forwarded-For 和 X-Real-IP

多级代理很少见,只有一级代理的情况下二者是等效的。如果有多级代理,x-forwarded-for效果是大于x-real-ip的,可以记录完整的代理链路。


如何获取真实IP

1、使用 X-Forwarded-For + realip模块

可以使用nginx的 ngx_http_realip_module 模块,从 X-Forwarded-For 或其他属性中提取真实IP。此处以 X-Forwarded-For 结合该模块为例子,需要做两件事:
1)、是请求途径的各代理需要设置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2)、是利用 realip 模块获取真实IP

这里proxy3的部分配置(proxy3将请求直接转发到后端服务),如下:

server {
    location / {
        set_real_ip_from 127.0.0.1;
        real_ip_header    X-Forwarded-For;
        real_ip_recursive on;
    }
}

set_real_ip_from: 表示从何处获取真实IP(解决安全问题,只认可自己信赖的IP),可以是IP或子网等,可以设置多个set_real_ip_from。
real_ip_header:表示从哪个header属性中获取真实IP
real_ip_recursive:递归检索真实IP,若从 X-Forwarded-For 中获取,则需递归检索;若像从X-Real-IP中获取,则无需递归。

2、使用X-Forwarded-For + 安全设置

由于客户端可以自行传递X-Forwarded-For,因此可以在第一个代理处重置其值,达到忽略客户端传递的X-Forwarded-For的效果。

在 proxy1 中进行如下配置:
proxy_set_header X-Forwarded-For $remote_addr;

3、使用 X-Real-IP

由于proxy1的 $remote_addr 是客户端真实IP,因此在 proxy1 中将X-Real-IP的值设置为 $remote_addr 即可。

proxy_set_header X-Real-IP $remote_addr;


proxy1 中设置了X-Real-IP的值,proxy2、proxy3日志中可以看到该值。

log_format proxyn '$http_x_real_ip "$request" $status';


HTTP 请求头可以随意构造,通过 curl 的 -H 参数构造 X-Forwarded-For 和 X-Real-IP来测试。
curl http://dev.freeoa.net:3000/ -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-IP: 2.2.2.2'

对于 Web 应用来说,X-Forwarded-For 和 X-Real-IP 就是两个普通的请求头,自然就不做任何处理原样输出了。这说明对于直连部署方式,除了从 TCP 连接中得到的 Remote Address 之外,请求头中携带的 IP 信息都不能相信。


结论

直接对外提供服务的 Web 应用,只能通过 Remote Address 获取 IP,不能相信任何请求头;同时还应该禁止 Web 应用直接对外提供服务;

使用 Nginx 等 Web 服务器进行反向代理的 Web 应用,在配置正确的前提下,要用 X-Forwarded-For 最后一节 或 X-Real-IP 来获取 IP(因为 Remote Address 得到的是 Nginx 所在服务器的内网 IP);


参考来源:
ngx_http_realip_module

HTTP 请求头中的 X-Forwarded-For

HTTP 请求头中的 X-Forwarded-For,X-Real-IP



服务对ipv4与ipv6的支持

Nginx可以同时支持ipv4与 ipv6的监听,但为了一致性的考虑,新版本Nginx推荐使用分开监听。

ipv4配置:
listen 80;

从Nginx 1.3版起默认ipv6only是打开的,所以只需要在监听中加入ipv6监听即可:
listen [::]:80 ipv6only=on;
listen 443 ssl;
listen [::]:443 ssl;

通过netstat或ss指令来查看侦听情况,如果列表出现 :::80的监听代表ipv6的监听已经成功。

1.如果只想监听ipv6,则去掉ipv4的配置,然后将ipv6设置为默认即可。
listen [::]:80 default ipv6only=on;
listen [::]:443 default ssl;

2.如果想监听指定ipv6地址,则将中括号中的:: 换成 指定ipv6地址即可。
listen [ipv6-addr]:80 default ipv6only=on;
listen [ipv6-addr]:443 default ssl;

使用ipv6测试站来测试是否已经可以正常访问。