Nginx+Lua+Redis 实现自动封禁访问频率过高IP

来自Linux78|wiki
Bob讨论 | 贡献2019年11月11日 (一) 16:00的版本 (创建页面,内容为“安装好redis ,openresty 与ngx_cache_purge模块,该模块用于清理nginx缓存 nginx_upstream_check_module模块,该模块用于ustream健康检查 ====…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

安装好redis ,openresty 与ngx_cache_purge模块,该模块用于清理nginx缓存 nginx_upstream_check_module模块,该模块用于ustream健康检查

在Nginx中使用Lua脚本访问Redis

连接redis,然后添加一些测试参数
[root@test1 utils]# redis-cli -h 192.168.1.222 -p 6379
192.168.1.222:6379> set "123" "456"
OK
编写链接Redis的Lua脚本
vim /usr/local/openresty/nginx/conf/lua/redis.lua
local redis = require "resty.redis"
local conn = redis.new()
conn.connect(conn, '192.168.1.222', '6379')     #根据自己情况写ip和端口号 
local res = conn:get("123")
if res==ngx.null then
   ngx.say("redis集群中不存在KEY——'123'")
   return
end
ngx.say(res)
在nginx配置文件中添加以下location
vim /usr/local/openresty/nginx/conf/nginx.conf
location / {
   root html;
}
location /lua {
   default_type text/plain;
   content_by_lua 'ngx.say("hello,lua")';
}
location /lua_redis {
       default_type text/plain;
       content_by_lua_file /usr/local/openresty/nginx/conf/lua/redis.lua;
   }
验证

在浏览器输入ip/lua_redis

看能不能获取到刚才在redis添加的"123" 这个key 并获取到他的值

如果能看到下图的内容表示可以访问redis

准备工作已经准备好了,现在要来最终的Nginx+Lua+Redis自动封禁并解封IP了

Nginx+Lua+Redis

添加访问控制的Lua脚本

(此脚本需要改的只有下面的这一句,把redis的ip和端口替换一下即可

ok, err = conn:connect(“192.168.1.222”, 6379)

温馨提示:如果在nginx的上层有用到阿里云的SLB负载均衡的话需要修改一下脚本里的所有…ngx.var.remote_addr

把remote_addr替换成从SLB获取真实IP的字段即可,不然获取到的IP全都是阿里云SLB发过来处理过的IP,全都是一个网段的,根本没有办法起到封禁的效果)

[root@test1 lua]# vim /usr/local/openresty/nginx/conf/lua/access.lua
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.222", 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()

2、在需要做访问限制的location里加两段代码即可,这里用刚才的/lua做演示

[root@test1 lua]# vim /usr/local/openresty/nginx/conf/nginx.conf
location / {
   root html;
}
location /lua {
   set $business "lua"; #加这个是为了知道存在redis的数据是哪个localtion
   access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;
   default_type text/plain;
   content_by_lua 'ngx.say("hello,lua")';
}
location /lua_redis {
       default_type text/plain;
       content_by_lua_file /usr/local/openresty/nginx/conf/lua/redis.lua;
   }

主要添加

access_by_lua_file /usr/local/openresty/nginx/conf/lua/access.lua;

让没一个请求都去调用这个lua脚本,注意路径和名字不要写错

set $business “lua” 是为了把IP放进redis的时候标明是哪个location的,可以不加

[root@test1 lua]# nginx -s reload #修改完后重启nginx

3、去访问192.168.1.222/lua 并一直按F5刷新

连接redis

[root@test1 ~]# redis-cli -h 192.168.1.222 -p 6379

发现redis已经在统计访问lua这个网页ip的访问次数了

192.168.1.22:6379> KEYS *
1) "lua-COUNT-192.168.1.11"
192.168.1.22:6379> GET  "lua-COUNT-192.168.1.11"
"8"

这个key的过期时间是30秒,如果30秒没有重复访问20次这个key就会消失,所以说正常用户一般不会触发这个封禁的脚本

192.168.1.22:6379>  ttl  "lua-COUNT-192.168.1.11"
(integer) 20
192.168.1.22:6379>  ttl  "lua-COUNT-192.168.1.11"
(integer) 19

当30秒内访问超过了20次 发现触发脚本了,变成了403 进到redis里发现多了一个lua-block-192.168.1.11 过期时间是300秒,就是说在300秒内这个ip无法继续访问192.168.1.222/lua这个页面了

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