Nginx获取用户真实ip

Nginx获取用户真实ip

为了网站的正常运行,所以我们要对网站的访问做一些限制,如果网站没有使用CDN,也就是用户直接与服务器相连,网站的访问方式为:用户浏览器->服务器。

如果网站使用了CDN,那么网站的访问方式就变成了:用户浏览器->CDN->服务器,这个时候方案一就会对CDN的IP做出限制,起到了相反的作用。

如果是第一种,此时 $remote_addr显示的正是用户的IP,如果使用CDN,那么remote_addr显示的是CDN的IP,remote_addr获取到的 IP 是 Web 服务器 TCP 连接的 IP(这个不能伪造,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求)

X-Forwarded-For 是一个扩展头,用来表示 HTTP 请求端真实 IP。

1
2
#X-Forwarded-For请求头格式非常简单
X-Forwarded-For:client, proxy1, proxy2

最远的为客户端IP,但是注意这个客户端IP是可以伪造的。有些CDN是提供realip(可能要企业用户才能使用,cf提供了CF-Connecting-IPTrue-Client-IP(仅Enterprise计划)),我们普通用户使用cf的话可以好好利用CF-Connecting-IP来获取用户真实IP。

获取真实IP

map需要放入nginx.conf或者site.conf中非server段

  1. 通过CloudFlare的CDN CF-Connecting-IP 获取真实IP(可信程度较高)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
map $HTTP_CF_CONNECTING_IP $real {
"" $remote_addr;
"~^([0-9a-fA-F:.]+)$" $HTTP_CF_CONNECTING_IP;
}
# 或者
map $HTTP_CF_CONNECTING_IP $real {
"" $remote_addr;
"~^(?<ipv6>[a-fA-F0-9:]+)$" $ipv6;
"~^(?<ipv4>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$" $ipv4;
default $HTTP_CF_CONNECTING_IP;
}
# 或者
map $HTTP_CF_CONNECTING_IP $real {
"" $remote_addr;
default $HTTP_CF_CONNECTING_IP;
}

# 如果 $HTTP_CF_CONNECTING_IP 的值是 IPv6 或 IPv4 地址,那么将 $HTTP_CF_CONNECTING_IP 赋值给 $real。
# 表示匹配以IPv4/6地址开头和结尾的字符串,并将匹配到的IPv4/6地址存储在名为ipv4/6的变量中。
# default $HTTP_CF_CONNECTING_IP; 如果HTTP_CF_CONNECTING_IP不匹配上述两个正则表达式,则将其值赋给real变量。
#这几种经过我测试都是正确的。
# $HTTP_CF_CONNECTING_IP是CF-Connecting-IP的值。
# Nginx 使用了 PCRE(Perl Compatible Regular Expressions)库来解析正则表达式,而 PCRE 支持"?<ipv4>"与"?P<ipv4>"这两种语法形式。
  1. 通过 http_x_forwarded_for 获取真实IP(可信程度较低)

1
2
3
4
5
6
map $http_x_forwarded_for $clientRealIP{
"" $remote_addr;
~^(?P<firstAddr>[0-9a-fA-F:.]+),?.*$ $firstAddr;
}
#要注意是否符合IP格式。
# $firstAddr是http_x_forwarded_for(X-Forwarded-For)值的第一个,容易被伪造。
  1. 通过nginx的realip模块获取真实地址(可信程度较低)

通过X-Forwarded-For获取用户真实IP,然后赋值给$remote_addr,但不是很可靠。

没有尝试,这只是一种方法。

可以尝试从$HTTP_CF_CONNECTING_IP获取用户真实IP,然后赋值给remote_addr。

  1. 没有使用CDN(可信程度较高)

如果没有使用CDN,那么直接使用 $remote_addr

1
proxy_set_header X-Real-IP $remote_addr;

HTTP_X_REAL_IP 的值为用户真实的ip。

总结:

我认为通过 CF-Connecting-IP 获取到的IP真实性高,强烈建议使用第一种,如果CDN提供商不是CloudFlare且没有提供客户端 IP 地址,那么只能使用第二种。

我们可以完全信任 HTTP_CF_CONNECTING_IP ,而不是 http_x_forwarded_for

获取到真实IP后就可以对这些IP的活动进行限制。

1
2
3
4
5
6
7
8
9
10
11
12
map $HTTP_CF_CONNECTING_IP $real {
"" $remote_addr;
"~^(?<ipv6>[a-fA-F0-9:]+)$" $ipv6;
"~^(?<ipv4>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$" $ipv4;
default $HTTP_CF_CONNECTING_IP;
}

# 第一行:map $HTTP_CF_CONNECTING_IP $real 定义了一个映射表,将变量 $HTTP_CF_CONNECTING_IP 的值映射到 $real 变量的值。
# 第二行:"" $remote_addr 是一个映射规则。当 $HTTP_CF_CONNECTING_IP 的值为空字符串时,将使用 $remote_addr(客户端的 IP 地址)作为 $real 的值。
# 第三行:"~^(?<ipv6>[a-fA-F0-9:]+)$" $ipv6 是另一个映射规则。它使用正则表达式匹配符合 IPv6 地址格式的字符串,并将匹配的内容保存到 $ipv6 变量中。如果匹配成功,将使用 $ipv6 作为 $real 的值。
# 第四行:"~^(?<ipv4>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$" $ipv4 是另一个映射规则。它使用正则表达式匹配符合 IPv4 地址格式的字符串,并将匹配的内容保存到 $ipv4 变量中。如果匹配成功,将使用 $ipv4 作为 $real 的值。
# 最后一行:default $HTTP_CF_CONNECTING_IP 是默认的映射规则。当前面的规则都不匹配时,将使用 $HTTP_CF_CONNECTING_IP 的值作为 $real 的值。

使用

我们获取到了用户的真实ip,那么我们要怎么使用他们呢?

以前都写过就不在赘述了。

日志

nginx

有一些变量需要改一下,比如map中的 $real , $clientRealIP

log_format main 中的 $real , $clientRealIP , $http_x_forwarded_for

log_formatmap 要放在server{}以外

应用

搭建图床

限制速率

vim /etc/nginx/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
http{
...
map $HTTP_CF_CONNECTING_IP $real {
"" $remote_addr;
default $HTTP_CF_CONNECTING_IP;
}

limit_conn_zone $real zone=perserver:10m;
limit_conn perserver 20; # 每个IP 最多同时建立20个连接
limit_conn_log_level notice;
limit_req_zone $real zone=one:10m rate=15r/s; # 每个IP允许每秒发送10个请求
limit_req_log_level notice;
limit_req_status 403; # 返回状态403!
limit_req zone=one burst=10 nodelay; # 瞬时请求不超过 5 个,不延迟处理请求
...
}
server{
...
#也可以加到每个站点配置中
#limit_conn perserver 50;
#limit_req zone=one burst=10 nodelay; # 不建议在子项中开启nodelay,如果有一些静态文件特别多很容易触发流控,可以适当增加这两个值。
#limit_rate_after 10m; #前10m不限速
#limit_rate 512k; #流量限制
...
}

我们需要知道的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1. 几个概念
remote_addr:如果中间没有代理,这个就是客户端的真实IP,如果有代理,这就是上层代理的IP.
X-Forwarded-For:一个HTTP扩展头,格式为 X-Forwarded-For: client, proxy1, proxy2
X-Real-IP:自定义的HTTP头,用于把客户端真实IP一层层传递下去。

2.几个变量
$remote_addr:上层IP(客户端或代理)
$proxy_add_x_forwarded_for:包括客户端请求头的X-Forwarded-For和$remote_addr
$http_x_forwarded_for:就是X-Forwarded-For的值

3. 获取客户端真实IP的方法
3.1 通过设置X-real-IP层层传递
首层代理:proxy_set_header X-Real-IP $remote_addr; 针对首层代理,拿到真实IP
非首层代理:proxy_set_header X-Real-IP $http_x_real_ip; # 针对非首层代理,一直传下去
3.2 通过设置X-Forwarded-For请求头
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
然后截取X-Forward-For请求头的第一段,即是客户端的真实IP

4. 经过CDN后,获取用户真实IP的方法
4.1 一般CDN都会传递X-Forwarded-For请求头,通过X-Forwarded-For请求头获取用户真实IP。
4.2 后端Nginx配置
http {
map $http_x_forwarded_for $clientRealIp {
"" $remote_addr;
~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr;
}

}
我们通过map自定义了一个变量$clientRealIp
如果X-Forwarded-For头是空的,那么客户端真实IP就是remote_addr;
如果X-Forwarded-For头非空,我们就通过正则匹配,捕获到第一段,这就是用户的真实IP,当然有可能是用户伪造的;
必须注意的是,在每一层代理都要设置X-Forwarded-For头。

安装压力测试软件

1
2
3
4
5
6
7
8
9
wget http://download.joedog.org/siege/siege-4.1.1.tar.gz
tar -zxvf siege-4.1.1.tar.gz

cd siege-4.1.1
./configure --with-ssl=/usr/bin/openssl
make clean
make && make install

siege -c 1000 -r 10000 -d 10 <url>

参考与引用:

nginx通过cloudflare cdn的请求头获取用户真实ip 兼容Ipv6

Cloudflare如何处理HTTP请求标头?

HTTP request headers

Restoring original visitor IPs

https://limbopro.com/archives/1852.html

https://www.imydl.tech/lnmp/231.html


Nginx获取用户真实ip
https://shyi.org/posts/43696/
作者
Shyi
发布于
2023年5月29日
更新于
2024年9月7日
许可协议