Nginx+Lua+Redis 实现自动封禁访问频率过高IP
安装好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秒将会重新开始计数