Nginx(或Openresty)的高级限制请求

Posted by Sunday on 2019-07-04

Nginx有ngx_http_limit_req_module可用于限制请求处理速率,但大多数人似乎只使用其基本功能:通过远程地址限制请求率,如下所示:

1
2
3
4
5
6
7
8
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
...
server{
...
location/search/{
limit_req zone=one burst=5;
}

这是从Nginx的官方文档中获取的示例配置,limit_req_zone指令将变量$binary_remote_addr作为限制传入请求的键。密钥填充名为one的区域,该区域由zone参数定义,它可以使用最多10m的内存。而rate参数表示每个$binary_remote_addr的最大请求率是每秒1。在搜索位置块中,我们可以使用limit_req指令来引用一个区域,突发不超过5个请求。

一切看起来都很棒,我们已经配置了Nginx来对抗流氓机器人/蜘蛛,对吧?

没有!该配置在现实生活中不起作用,它永远不应该在您的生产环境中使用!以下列情况为例:

  • 当用户在NAT后访问您的网站时,他们共享相同的公共IP,因此Nginx将仅使用一个$binary_remote_addr来执行限制请求。总共有数百名用户每天只能访问您的网站1次!
  • 僵尸网络用于抓取您的网站,每次使用不同的IP地址。同样,在这种情况下,限制$binary_remote_addr是完全没用的。

那么我们应该使用什么配置呢?我们需要使用不同的变量作为键,或者甚至将多个变量组合在一起(从版本1.7.6开始,limit_req_zone的键可以采用多个变量)。而不是远程地址,最好使用请求HTTP标头来区分用户,例如User-Agent,Referer,Cookie等。这些标头在Nginx中很容易访问,它们作为内置变量公开,如$http_user_agent,$http_referer,$cookie_ name等

例如,这是定义区域的更好方法:

1
2
3
http {
limit_req_zone $binary_remote_addr$http_user_agent zone=two:10m rate=90r/m;
}

它将$binary_remote_addr和$http_user_agent组合在一起,因此可以区分NATed网络后面的不同用户代理。但它仍然不完美,多个用户可以使用相同的浏览器,相同的版本,因此他们发送相同的User-Agent标头!另一个问题是$http_user_agent变量的长度不固定(与$binary_remote_addr不同),长标头可能会使用该区域的大量内存,可能超过它。

为了解决第一个问题,我们可以在那里使用更多变量,cookie会很棒,因为不同的用户发送他们独特的cookie,比如$cookie_ userid,但这仍然是我们的第二个问题。答案是使用变量哈希代替。

Thers是一个名为set-misc-nginx-module的第三方模块,我们可以用它来从变量生成哈希值。如果您使用的是Openresty,则已包含此moule。所以配置是这样的:

1
2
3
4
5
6
7
8
9
10
11
http {
...
limit_req_zone $binary_remote_addr$cookie_hash$ua_hash zone=3:10m rate=90r/m;
...
server{
...
set_md5 $cookie_hash $cookie_userid;
set_md5 $ua_hash $http_user_agent;
...
}
}

我们可以在http块中使用$cookie_hash和$ua_hash,然后在server块中定义它们。这个配置现在很棒。

现在让我们继续解决分布式僵尸网络问题,我们需要从密钥中取出$binary_remote_addr,因为这些机器人通常不会发送Referer标头(否则你可以自己找到它的独特之处),我们可以利用它。这个配置应该照顾它:

1
2
3
4
5
6
7
8
9
10
11
12
http {
...
limit_req_zone $cookie_hash $referer_hash $ua_hash zone=3:10m rate=90r/m;
...
server{
...
set_md5 $cookie_hash$cookie_userid;
set_md5 $referer_hash $http_referer;
set_md5 $ua_hash $http_user_agent;
...
}
}

Nginx(或Openresty)的高级限制请求