15人参与 • 2026-04-27 • SSL
因为 nginx 的 lua 模块(openresty)中,access_by_lua_block 阶段执行时,请求尚未进入 upstream 或日志阶段,但若后续有 rewrite_by_lua_block 或其他模块重写 uri、跳转,可能绕过该阶段;更关键的是,它不自动阻断后续处理——你得显式调用 ngx.exit(403),否则脚本跑完就继续往下走。
常见错误现象:ngx.var.remote_addr 被封了,但请求仍能访问后端;或日志里看到 ip 出现在黑名单里,http 状态码却是 200。
ngx.exit(403),不能只设变量或写 redistry_files 或 error_page 重定向,要确认它们不会覆盖或跳过 access_by_lua_blockaccess_by_lua_block 中做耗时操作(如 http 请求),会阻塞 worker 进程本地内存(如 shared_dict)适合短时频控,但跨 worker、跨实例的黑名单必须依赖外部存储。redis 是最常用选择,关键是用原子操作避免并发查改导致漏放行。
推荐用 eval 执行一段内联 lua 脚本,一次性完成「查是否存在」+「若存在则返回 1」,不依赖两次网络往返:
local exists = redis.call("exists", "blacklist:" .. keys[1])
if exists == 1 then
return 1
else
return 0
end在 nginx 配置中调用:
access_by_lua_block {
local red = require "resty.redis"
local r = red:new()
r:set_timeout(100)
local ok, err = r:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.err, "failed to connect to redis: ", err)
return
end
local ip = ngx.var.remote_addr
local res, err = r:eval(
"return redis.call('exists', 'blacklist:' .. argv[1])",
0, ip
)
if res == 1 then
ngx.exit(403)
end
}get + if 两步,竞态下可能刚查完就被删掉blacklist:)并设置 ttl,避免长期堆积resty.redis.connect + set_keepalivereload 会中断连接、丢失共享字典状态,不适合高频更新的黑名单。真正可行的方式是:把规则存 redis,nginx 每次请求都实时查——只要 redis 响应够快(通常如果你真想降低 redis 查询压力,可以用双层缓存:先查 shared_dict,未命中再查 redis,并异步刷新本地缓存(用 lua-resty-lock 防穿透):
local dict = ngx.shared.blacklist_cache
local ip = ngx.var.remote_addr
local cached = dict:get(ip)
if cached == 1 then
ngx.exit(403)
end
-- 加锁后查 redis 并回填
local lock = require "resty.lock":new("locks")
local elapsed, err = lock:lock(ip)
if not elapsed then
ngx.log(ngx.err, "failed to acquire lock: ", err)
return
end
local red = --[[...redis init...]]
local res, _ = red:eval("return redis.call('exists', 'blacklist:' .. argv[1])", 0, ip)
if res == 1 then
dict:set(ip, 1, 60) -- 缓存 60 秒
ngx.exit(403)
end
lock:unlock()timer.at)同步 redis 到 shared_dict,worker 间不同步且易超时scan + 本地 bloom filter 预筛,但实现复杂度陡增最安全的做法是提供独立管理接口(比如一个仅限内网访问的 /api/unban),由运维或自动化脚本调用,而不是手动改 redis 或 reload。
示例接口只需一行命令:
location /api/unban {
allow 10.0.0.0/8;
deny all;
content_by_lua_block {
local ip = ngx.var.arg_ip
if not ip or #ip == 0 then
ngx.status = 400
ngx.say("missing ip")
return
end
local red = require "resty.redis":new()
red:set_timeout(100)
red:connect("127.0.0.1", 6379)
red:del("blacklist:" .. ip)
red:del("blacklist_cache:" .. ip) -- 清本地缓存
ngx.say("ok")
}
}del * 或无白名单的批量操作接口del 是原子的,但 shared_dict 清除需各 worker 协同,所以建议用 key 名一致的命名空间(如都加 blacklist: 前缀),便于脚本批量清理ngx.log(ngx.info, ...)),排查误封时比翻 access.log 直观得多实际部署时最容易被忽略的是 redis 连接失败后的降级策略——默认行为是放行,但有些场景要求“宁可误杀也不能漏封”,这时就得在 redis:connect 失败时主动 ngx.exit(503),而不是静默跳过。这个决策点不在代码里,而在你的安全等级定义中。
到此这篇关于nginx中lua脚本实现动态黑名单自动封禁机制的文章就介绍到这了,更多相关nginx lua动态黑名单自动封禁内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论