背景:访问时不时会被暴力刷量,爬虫和恶意攻击导致数据库,服务等瘫痪
需求:在nginx上实现一个动态拦截ip的方法,具体是当某个ip在1分钟内访问超过60次时,将其加入redis并拦截,拦截时间默认1天。
技术选型:使用nginx+lua+redis的方法。这种方案通过lua脚本在nginx处理请求时检查redis中的黑名单,同时统计访问频率,超过阈值就封禁。这应该符合用户的需求。
需要结合lua脚本和redis的计数功能。安装openresty,配置nginx的lua模块,编写lua脚本统计访问次数,使用redis存储和过期键,以及设置拦截逻辑。连接池的使用,避免频繁连接redis影响性能。
一、环境准备
- 安装openresty
openresty集成了nginx和lua模块,支持直接运行lua脚本:
# ubuntu/debian sudo apt-get install openresty # centos yum install openresty
- 安装redis服务
sudo apt-get install redis-server # debian系 sudo yum install redis # redhat系
二、nginx配置
主配置文件(nginx.conf)在
http块中添加共享内存和lua脚本路径:
http {
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
lua_shared_dict ip_limit 10m; # 共享内存区
server {
listen 80;
server_name _;
location / {
access_by_lua_file /usr/local/lua/ip_block.lua; # 核心拦截脚本
root /var/www/html;
}
}
}
三、lua脚本实现动态拦截
脚本路径创建lua脚本:
/usr/local/lua/ip_block.lua脚本内容
local redis = require "resty.redis"
local red = redis:new()
-- redis连接参数
local redis_host = "127.0.0.1"
local redis_port = 6379
local redis_timeout = 1000 -- 毫秒
local redis_auth = nil -- 无密码留空
-- 拦截参数
local block_time = 86400 -- 封禁时间(1天)
local time_window = 60 -- 统计窗口(1分钟)
local max_requests = 60 -- 最大请求数
-- 获取客户端ip
local function get_client_ip()
local headers = ngx.req.get_headers()
return headers["x-real-ip"] or headers["x_forwarded_for"] or ngx.var.remote_addr
end
-- 连接redis
local function connect_redis()
red:set_timeout(redis_timeout)
local ok, err = red:connect(redis_host, redis_port)
if not ok then
ngx.log(ngx.err, "redis连接失败: ", err)
return nil
end
if redis_auth then
local ok, err = red:auth(redis_auth)
if not ok then ngx.log(ngx.err, "redis认证失败: ", err) end
end
return ok
end
-- 主逻辑
local client_ip = get_client_ip()
local counter_key = "limit:count:" .. client_ip
local block_key = "limit:block:" .. client_ip
-- 检查是否已封禁
local is_blocked, err = red:get(block_key)
if tonumber(is_blocked) == 1 then
ngx.exit(ngx.http_forbidden) -- 直接返回403
end
-- 统计请求次数
connect_redis()
local current_count = red:incr(counter_key)
if current_count == 1 then
red:expire(counter_key, time_window) -- 首次设置过期时间
end
-- 触发封禁条件
if current_count > max_requests then
red:setex(block_key, block_time, 1) -- 封禁并设置1天过期
red:del(counter_key) -- 删除计数器
ngx.exit(ngx.http_forbidden)
end
-- 释放redis连接
red:set_keepalive(10000, 100)
四、性能优化
- redis连接池通过
set_keepalive复用连接,避免频繁建立tcp连接
- 共享内存缓存使用
lua_shared_dict缓存高频访问ip,减少redis查询压力
异步日志记录封禁操作异步写入日志文件,避免阻塞请求处理:
ngx.timer.at(0, function()
local log_msg = string.format("%s - ip %s blocked at %s",
ngx.var.host, client_ip, ngx.localtime())
local log_file = io.open("/var/log/nginx/blocked_ips.log", "a")
log_file:write(log_msg, "\n")
log_file:close()
end)
五、验证与测试
- 手动触发封禁
# 模拟高频请求 ab -n 100 -c 10 http://your-domain.com/ # 检查redis redis-cli keys "limit:block:*"
- 自动解封验证
等待24小时后检查封禁ip是否自动删除:
redis-cli ttl "limit:block:1.2.3.4" # 返回剩余秒数
六、扩展方案
- 分布式封禁
在多台nginx服务器间共享redis黑名单,实现集群级拦截 可视化监控
通过grafana+prometheus展示实时拦截数据:
# 采集redis指标 prometheus-redis-exporter --redis.address=localhost:6379
- 动态调整阈值
通过redis hash存储不同路径的拦截规则:
local rule_key = "limit:rule:" .. ngx.var.uri local custom_rule = red:hget(rule_key, "max_requests")
引用说明
- 核心拦截逻辑参考了nginx+lua+redis的经典架构设计
- redis键过期机制确保自动解封
- 性能优化方案借鉴了openresty最佳实践
以上就是nginx实现动态拦截非法访问ip的方法的详细内容,更多关于nginx动态拦截ip的资料请关注代码网其它相关文章!
发表评论