|
-- 自动将访问频次过高的IP地址加入黑名单封禁一段时间
-- ================= 配置参数 =================
-- 连接池超时回收(毫秒)
local pool_max_idle_time = 10000
-- 连接池大小
local pool_size = 100
-- Redis连接超时时间(毫秒)
local redis_connection_timeout = 100
-- Redis主机
local redis_host = "your_redis_host_ip"
-- Redis端口
local redis_port = "6379"
-- Redis认证密码
local redis_auth = "your_redis_password"
-- 封禁IP时间(秒)
local ip_block_time = 120
-- 指定IP访问频率时间段(秒)
local ip_time_out = 1
-- 指定IP访问频率计数最大值(次)
local ip_max_count = 3
-- ================= 工具函数 =================
-- 错误日志记录
local function errlog(msg, ex)
ngx.log(ngx.ERR, msg, ex)
end
-- 释放连接池
local function close_redis(red)
if not red then
return
end
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say("redis connct err:", err)
return red:close()
end
end
-- 获取客户端真实IP
local function getIp()
local clientIP = ngx.req.get_headers()["X-Real-IP"]
if clientIP == nil then
clientIP = ngx.req.get_headers()["x_forwarded_for"]
end
if clientIP == nil then
clientIP = ngx.var.remote_addr
end
return clientIP
end
-- ================= Redis连接 =================
local redis = require "resty.redis"
local client = redis:new()
local ok, err = client:connect(redis_host, redis_port)
-- 连接失败返回服务器错误
if not ok then
close_redis(client)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 设置超时时间
client:set_timeout(redis_connection_timeout)
-- 优化验证密码操作:代表连接在连接池使用的次数
-- 如果为0代表未使用,不为0代表复用,在只有为0时才进行密码校验
local connCount, err = client:get_reused_times()
-- 新建连接,需要认证密码
if 0 == connCount then
local ok, err = client:auth(redis_auth)
if not ok then
errlog("failed to auth: ", err)
return
end
elseif err then
-- 从连接池中获取连接出错
errlog("failed to get reused times: ", err)
return
end
-- ================= 业务逻辑 =================
local cliendIp = getIp()
local incrKey = "limit:count:" .. cliendIp
local blockKey = "limit:block:" .. cliendIp
-- 查询IP是否被禁止访问,如果存在则返回403错误代码
local is_block, err = client:get(blockKey)
if tonumber(is_block) == 1 then
close_redis(client)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- 增加访问计数
local ip_count, err = client:incr(incrKey)
if tonumber(ip_count) == 1 then
client:expire(incrKey, ip_time_out)
end
-- 如果超过单位时间限制的访问次数,则添加限制访问标识
if tonumber(ip_count) > tonumber(ip_max_count) then
client:set(blockKey, 1)
client:expire(blockKey, ip_block_time)
end
-- 释放Redis连接
close_redis(client)
|