Nginx postread阶段 http_realip_module获取用户端真实IP_运维

 

realip模块和如何获取用户端真实IP


postread阶段的realip模块可以帮助我们发现用户真实的IP地址,这为我们后续的一些模块实现例如限速,限流等等功能提供了可能性。(http_realip_module模块是没有编译进Nginx需要使用with)如何拿到用户真实IP地址呢?

Nginx postread阶段 http_realip_module获取用户端真实IP_ip地址_02

TCP连接四元组里面的src ip,src port,根据该条连接的src ip就能够判断出用户的IP地址了。但是实际是中间还包含反向代理,因为网络当中存在着很多的反向代理,反向代理会导致会导致与上游的机器又建立了一个新的TCP连接,所以上游的服务器想通过TCP连接里面的src ip是无法拿到原始用户IP地址的

 

比如说我们在家里上网的时候,家里的路由器给我分配了内网IP地址192.168.0.x。当我通过运营商比如电信,电信可能为我分配了一个公网的IP地址,然后我去访问某一个网站的时候,可能我会先命中到它的CDN,这个网站使用CDN来加速,比如对一些图片加速。由CDN到反向代理服务器建立了TCP的连接,再由反向代理服务器到Nginx建立了TCP连接。

如果通过拿IP地址拿到的是反向代理的IP地址2.2.2.2,其实我们要拿用户的IP地址不是用户内网的IP地址是于压缩公网IP地址115.204.33.1。如果我要做限速或者做并发连接的控制需要基于运营商分配的IP来控制。

 

现在Nginx拿到是remote_addr变量的值2.2.2.2,其实我想要拿到的用户IP地址是115.204.33.1,怎么样才可以做到呢??

通过以下两点,HTTP协议里面的X-Forwarded-For头部和X-Real-IP头部:

X-Real-IP只能添加一个IP,客户端用户真实的IP。X-Forwarded-For是逐步添加的,包含了客户端用户真实IP和经过的所有反向代理服务器的IP。所以要获取客户端用户真实的IP可以通过X-Real-IP获取也可以通过X-Forwarded-For获取。

 

CDN 下 nginx 获取用户真实 IP 地址


随着 nginx 的迅速崛起,越来越多公司将 apache 更换成 nginx. 同时也越来越多人使用 nginx 作为负载均衡, 并且代理前面可能还加上了 CDN 加速,但是随之也遇到一个问题: nginx 如何获取用户的真实 IP 地址。
实例环境:
用户 IP 120.22.11.11
CDN 前端 61.22.22.22
CDN 中转 121.207.33.33
公司 NGINX 前端代理 192.168.50.121(外网 121.207.231.22)
 

1、使用 CDN 自定义 IP 头来获取
假如说你的 CDN 厂商使用 nginx,那么在 nginx 上将$remote_addr 赋值给你指定的头,方法如下:
proxy_set_header remote-user-ip $remote_addr;

proxy_set_header也可以自定义参数,如:proxy_set_header test paroxy_test;

如果想要支持下划线的话,需要增加如下配置:

underscores_in_headers on;

后端 PHP 代码 getRemoteUserIP.php

<?php
$ip = getenv("HTTP_REMOTE_USER_IP");
echo $ip;
?>

访问 getRemoteUserIP.php,结果如下:
120.22.11.11       //取到了真实的用户 IP,如果 CDN 能给定义这个头的话,那这个方法最佳
 

 

2、通过 HTTP_X_FORWARDED_FOR 获取 IP 地址
一般情况下 CDN 服务器都会传送 HTTP_X_FORWARDED_FOR 头,这是一个 ip 串,后端的真实服务器获取
HTTP_X_FORWARDED_FOR 头,截取字符串第一个不为 unkown 的 IP 作为用户真实 IP 地址, 例如:
120.22.11.11,61.22.22.22,121.207.33.33,192.168.50.121(用户 IP,CDN 前端 IP,CDN 中转,公司 NGINX 代理)

getFor.php
<?php
$ip = getenv("HTTP_X_FORWARDED_FOR");
echo $ip;
?>

访问 getFor.php 结果如下:
120.22.11.11,61.22.22.22,121.207.33.33,192.168.50.121

如果你是 php 程序员,你获取第一个不为 unknow 的 ip 地址,这边就是 120.22.11.11.

 

3.使用 nginx 自带模块 realip 获取用户 IP 地址

The ​​ngx_http_realip_module​​​ module is used to change the client address and optional port to those sent in the specified header field.This module is not built by default, it should be enabled with the ​​--with-http_realip_module​​ configuration parameter.Example Configuration:

set_real_ip_from 192.168.1.0/24; set_real_ip_from 192.168.2.1; set_real_ip_from 2001:0db8::/32; real_ip_header X-Forwarded-For; real_ip_recursive on;

Directives


Syntax:

set_real_ip_from address | CIDR | unix:;​

Default:


Context:

​http​​​, ​​server​​​, ​​location​

Syntax:

real_ip_recursive on | off;​

Default:

real_ip_recursive off;

Context:

​http​​​, ​​server​​​, ​​location​

If recursive search is disabled, the original client address that matches one of the trusted addresses is replaced by the last address sent in the request header field defined by the ​​real_ip_header​​ directive. If recursive search is enabled, the original client address that matches one of the trusted addresses is replaced by the last non-trusted address sent in the request header field.

Syntax:

real_ip_header field | X-Real-IP | X-Forwarded-For | proxy_protocol;​

Default:

real_ip_header X-Real-IP;

Context:

​http​​​, ​​server​​​, ​​location​

Defines the request header field whose value will be used to replace the client address.

 

安装 nginx 之时加上 realip 模块,我的参数如下:
./configure --prefix=/usr/local/nginx --with-http_realip_module

真实服务器 nginx 配置
server {
listen 80;
server_name www.test.com;
access_log /data/logs/nginx/www.test.com.access.log main;
index index.php index.html index.html;
root /data/site/www.test.com;

location /
{
root /data/site/www.test.com;
}

location = /getRealip.php
{
set_real_ip_from 192.168.50.0/24;
set_real_ip_from 61.22.22.22; #CDN 前端
set_real_ip_from 121.207.33.33; #CDN 中转
set_real_ip_from 127.0.0.1;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

fastcgi_pass unix:/var/run/phpfpm.sock;
fastcgi_index index.php;
include fastcgi.conf;
}
}

getRealip.php 内容

<?php
$ip = $_SERVER['REMOTE_ADDR'];
echo $ip;
?>

访问 www.test.com/getRealip.php,返回:
120.22.11.11
如果注释 real_ip_recursive on 或者 real_ip_recursive off,访问 www.test.com/getRealip.php,返回:
121.207.33.33

很不幸,获取到了中继的 IP,real_ip_recursive 的效果看明白了吧?

set_real_ip_from:真实服务器上一级代理的 IP 地址或者 IP 段,可以写多行
real_ip_header:从哪个 header 头检索出要的 IP 地址
real_ip_recursive:默认值是off,递归排除 IP 地址,ip 串从右到左开始排除 set_real_ip_from 里面出现的 IP,如果出现了未出现这些 ip 段的 IP,那么这个 IP 将被认为是用户的 IP。例如我这边的例子,真实服务器获取到的 IP 地址串如下:
120.22.11.11,61.22.22.22,121.207.33.33,192.168.50.121

在 real_ip_recursive on 的情况下:61.22.22.22,121.207.33.33,192.168.50.121 都出现在 set_real_ip_from 中,仅仅 120.22.11.11 没出现,那么他就被认为是用户的 ip 地址,并且赋值到 remote_addr 变量

在 real_ip_recursive off 或者不设置的情况下:192.168.50.121 出现在 set_real_ip_from 中排除掉,接下来的 ip 地址便认为是用户的 ip 地址

 

如果仅仅如下配置:
set_real_ip_from 192.168.50.0/24;
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
访问结果如下:
121.207.33.33
 

三种在 CDN 环境下获取用户 IP 方法总结


CDN 自定义 header 头
优点:获取到最真实的用户 IP 地址,用户绝对不可能伪装 IP
缺点:需要 CDN 厂商提供

获取 forwarded-for 头
优点:可以获取到用户的 IP 地址
缺点:程序需要改动,以及用户 IP 有可能是伪装的

使用 realip 获取
优点:程序不需要改动,直接使用 remote_addr 即可获取 IP 地址
缺点: ip 地址有可能被伪装,而且需要知道所有 CDN 节点的 ip 地址或者 ip 段