Nginx使用Lua+Redis限制IP的访问频率

不登高山,不知天之高也;不临深溪,不知地之厚也。

添加访问控制的Lua脚本

1
[root@hbase31 ~]# vim /usr/local/openresty/nginx/conf/lua/access.lua
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
44
local ip_block_time=300 --封禁IP时间(秒)
local ip_time_out=30 --指定ip访问频率时间段(秒)
local ip_max_count=20 --指定ip访问频率计数最大值(秒)
local BUSINESS = ngx.var.business --nginx的location中定义的业务标识符

--连接redis
local redis = require "resty.redis"
local conn = redis:new()
ok, err = conn:connect("192.168.1.30", 6379)
conn:set_timeout(2000) --超时时间2秒

--如果连接失败,跳转到脚本结尾
if not ok then
goto FLAG
end

--查询ip是否被禁止访问,如果存在则返回403错误代码
is_block, err = conn:get(BUSINESS.."-BLOCK-"..ngx.var.remote_addr)
if is_block == '1' then
ngx.exit(403)
goto FLAG
end

--查询redis中保存的ip的计数器
ip_count, err = conn:get(BUSINESS.."-COUNT-"..ngx.var.remote_addr)

if ip_count == ngx.null then --如果不存在,则将该IP存入redis,并将计数器设置为1、该KEY的超时时间为ip_time_out
res, err = conn:set(BUSINESS.."-COUNT-"..ngx.var.remote_addr, 1)
res, err = conn:expire(BUSINESS.."-COUNT-"..ngx.var.remote_addr, ip_time_out)
else
ip_count = ip_count + 1 --存在则将单位时间内的访问次数加1

if ip_count >= ip_max_count then --如果超过单位时间限制的访问次数,则添加限制访问标识,限制时间为ip_block_time
res, err = conn:set(BUSINESS.."-BLOCK-"..ngx.var.remote_addr, 1)
res, err = conn:expire(BUSINESS.."-BLOCK-"..ngx.var.remote_addr, ip_block_time)
else
res, err = conn:set(BUSINESS.."-COUNT-"..ngx.var.remote_addr,ip_count)
res, err = conn:expire(BUSINESS.."-COUNT-"..ngx.var.remote_addr, ip_time_out)
end
end

-- 结束标记
::FLAG::
local ok, err = conn:close()

这个脚本的目的很简单:一个IP如果在30秒内其访问次数达到20次则表明该IP访问频率太快了,因此将该IP封禁5分钟。同时由于计数的KEY在Redis中的超时时间设置成了30秒,所以如果两次访问间隔时间大于30秒将会重新开始计数

在Nginx需要限速的location中引用上述脚本

1
2
3
4
5
6
7
8
9
location /user/ {
set $business "USER";
access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://user_224/user/;
}

注:对于有大量静态资源文件(如:js、css、图片等)的前端页面可以设置只有指定格式的请求才进行访问限速,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
location /h5 {
if ($request_uri ~ .*\.(html|htm|jsp|json)) {
set $business "H5";
access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;
}
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://h5_224/h5;
}

https://juejin.im/entry/5a45a9b8f265da431f4b62d5