关于Laravel 与 Nginx 限流策略防止恶意请求

一、问题背景

最近公司最近的几台线上服务器经常出现CPU覆盖过高,影响部分应用响应超时,产生了大量的短信和邮件报警,经过排查数据库日志和access.log,发现是API接口被刷,被恶意疯狂请求,最大一次大概120次/s。

之前没有过太多这方面经验,处理起来不是很顺畅,这次的问题刚好提了醒,经过这次的问题暴露,来记录一下解决方案和策略。

线上的部署方案是:nginx + laravel。

首先我们尝试从nginx层面入手,将占用更少的内存消耗,无需再转发到php-fpm上处理。

二、(恶意)请求特征

想好很好的特征,就必须捕捉一定的特征,通过这个特征来有效的控制到恶意请求。

短时间内,IP对某接口产生大量请求
user_agent,非正常信息或为空
请求量比平时要高涨很多。

三、限流策略(nginx)

限制请求数

首先的话.就是控制单IP时间上的请求次数和IP连接数,配置如下:

http {

limit_req_zone $binary_remote_addr zone=one:1m rate=1r/s;

server {
    location /api/ {
        limit_req zone=one burst=5;
    }
}

}

limit_req_zone主要控制单个IP的请求速率,使用漏桶算法来完成限制,limit_req_zone size,主要用于储存统计IP的请求信息,1M可以储存16000个IP,当每秒的请求超过了16000个的时候,其余访问都会被访问503 服务暂不可用。

上面的模板设置了,每秒最大不超过1个请求,最大延迟请求不超过5个。

如果我们的服务器每次接口的响应时间是在200ms-300ms,那我们对应的每秒的限制就应该设置成 1000ms / 接口响应耗时。

限制并发连接数

限制完用户请求频率后,如果仍然还是存在很大的恶意请求,我们还可以进行并发数的限制。

http {

limit_conn_zone $binary_remote_addr zone=one:1m;

server {
    location /api/ {
       limit_conn one 10;
    }
}

}

limit_conn_zone:主要是用于控制请求并发数,频率不能太快。

limit_conn_zone size跟limit_req_zone意思一致,可根据需要动态调控,上面的案例中,表示限制每个客户端IP最大并发连接数为10。

设定IP黑名单

当某个IP请求过于频繁或者需要完全杜绝该IP的访问时,可以通过nginx的deny配置来禁止黑名单中的IP访问。

http {

include blockip.conf;

}

黑名单配置

deny 195.91.112.66;

deny 192.168.2.100;

被添加黑名单后,再次访问就会出现403禁止访问.

image

限制UA(user-agent)信息

http {

server {

if ($http_user_agent ~* "curl") {

return 403;

}

}

}

上面就禁止了ua信息为curl的客户端,直接返回403。

禁止多个ua,通过|来隔断。

if ($http_user_agent ~* "curl|wget") {

return 403;

}

(四)、限流策略(laravel)

在我们的laravel项目中,存在一个Throttle中间件,该策略,可以在应用层上面,有效抑制用户可以恶意请求,配置如下:

Route::group(['middleware' => 'throttle:30:1'],function(){

Route::any('/login', 'LoginController@login');

});

在throttle配置中,第一个参数控制了请求数,第二个参数用于控制请求频率,上面的配置表明,每个客户端IP每分钟最大请求30次 login路由。

当客户端ip超出请求限制后,服务端就会返回429 Too Many Attempts.响应

使用场景

最近,报告查询系统负载均衡集群相关配置已经完成,两种实现方式分别是基于Ehcache和Redis的session管理策略。

大家都知道服务器资源有限的,但是客户端来的请求是无限的(不排除恶意攻击), 为了保证大部分的请求能够正常响应,不得不放弃一些客户端来的请求,所以我们会采用Nginx的限流操作, 这种操作可以很大程度上缓解服务器的压力, 使其他正常的请求能够得到正常响应。

如何使用Nginx实现基本的限流,比如单个IP限制每秒访问50次。通过Nginx限流模块,我们可以设置一旦并发连接数超过我们的设置,将返回503错误给客户端。这样可以非常有效的防止CC攻击。再配合 iptables防火墙,基本上CC攻击就可以无视了。

如何使用

conf配置

限制请求

limit_req_zone $binary_remote_addr $uri zone=api_read:20m rate=50r/s;

按ip配置一个连接 zone

limit_conn_zone $binary_remote_addr zone=perip_conn:10m;

按server配置一个连接 zone

limit_conn_zone $server_name zone=perserver_conn:100m;

server {

listen 80;

server_name report.52itstyle.com;

index login.jsp;

location / {

#请求限流排队通过 burst默认是0

limit_req zone=api_read burst=5;

#连接数限制,每个IP并发请求为2

limit_conn perip_conn 2;

#服务所限制的连接数(即限制了该server并发连接数量)

limit_conn perserver_conn 1000;

#连接限速

limit_rate 100k;

proxy_pass http://report;

}

}

upstream report {

fair;

server 172.16.1.120:8882 weight=1 max_fails=2 fail_timeout=30s;

server 172.16.1.120:8881 weight=1 max_fails=2 fail_timeout=30s;

}

配置503错误

默认情况,超出限制额度,将会报503错误,提示:

503 Service Temporarily Unavailable

The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later. Sorry for the inconvenience.

Please report this message and include the following information to us.

Thank you very much!

这样显示没毛病,但是不够友好,这里我们自定义503错误。

error_page 500 502 503 504 /50x.html;

location = /50x.html {

root html;#自定义50X错误

}

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
33
34
35
36
37
38
39
40
41
42
43

配置说明

limit_conn_zone

是针对每个IP定义一个存储session状态的容器。这个示例中定义了一个100m的容器,按照32bytes/session,可以处理3200000个session。

limit_rate 300k;

对每个连接限速300k. 注意,这里是对连接限速,而不是对IP限速。如果一个IP允许两个并发连接,那么这个IP就是限速limit_rate×2。

burst=5;

这相当于在检查站req旁边放5个座位。如果某个请求当时超过速度限制被拦了,请他在空座位上坐着,等排队,如果检查站空了,就可以通过。如果连座位都坐满了,那就抱歉了,请求直接退回,客户端得到一个服务器忙的响应。所以说burst跟request_rate一点关系都没有,设成10000,就是1万个请求可以等着排队,而检查站还是1秒钟放行5个请求(龟速)。而且也不能一直排队,所以nginx还设了超时,排队超过一定时间,也是直接退回,返回服务器忙的响应。

以上配置Nginx需要配置以下模块:

ngx_http_limit_conn_module (static)

ngx_http_limit_req_module (static)

执行命令 nginx -V 就可以检查到是否有安装。

小结

限流的目的就是防止恶意请求流量,恶意攻击,或者防止流量超出系统峰值。

实现方案有很多,Nginx的limit模块只是其中一个思路。对于恶意请求流量,限制访问到cache层或者对于恶意ip使用nginx deny进行屏蔽。