构建动态IP黑名单:Nginx+Lua+Redis实战方案 服务器时不时遭遇恶意爬虫或攻击者的频繁请求,
服务器时不时遭遇恶意爬虫或攻击者的频繁请求,怎么办?一个行之有效的防御策略是建立动态的IP黑名单。简单说,就是把那些“不受欢迎”的IP地址列入名单,在一段时间内拒绝其所有访问请求。这不仅能有效拦截恶意流量,还能灵活设置封禁时长,实现自动化管理。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
我们的目标很明确:搭建一个能够动态更新、支持分布式共享、且对应用性能影响极小的IP黑名单系统。在开始动手前,需要准备好以下环境:
服务器系统: CentOS 7 或 Ubuntu 等主流Linux发行版均可。
Redis版本: 5.0.5 或更高。
Nginx版本: 推荐使用集成Lua模块的OpenResty,它让在Nginx中嵌入Lua逻辑变得轻而易举。
实现IP黑名单,路子其实不少,各有优劣:
1. 操作系统层面(如iptables)
直接在服务器防火墙上动手。优点是拦截彻底,效果立竿见影。但缺点也很明显:每次操作都得登录服务器手动修改配置,既麻烦又难以应对快速变化的攻击,更别提在多台服务器间同步名单了。
2. Web服务器层面(如Nginx)
利用Nginx自身的deny指令或强大的Lua扩展模块。这个方案的魅力在于“动态化”——可以通过程序逻辑实时更新黑名单,并能方便地设置过期时间。一套配置可以分发到多台Nginx服务器,实现分布式封禁。当然,它需要你稍微了解一下Lua脚本和Nginx配置。
3. 应用层面
在业务代码处理请求前,先检查IP是否在黑名单里。这种方式对开发者最友好,容易理解和维护。但问题在于,它增加了应用的逻辑负担,在高并发场景下可能成为性能瓶颈,而且代码容易变得冗长。
综合来看,为了兼顾灵活性、性能和易管理性,Nginx + Lua + Redis的组合脱颖而出。它利用Redis作为中心化的存储和共享点,通过Lua脚本在Nginx的访问阶段进行高效判断,几乎不影响正常业务响应速度。
上图清晰地展示了这个工作流程:请求到达Nginx,由Lua脚本查询Redis中的黑名单,并决定是放行还是拒绝。
配置起来并不复杂。首先,在你需要启用IP黑名单的站点配置中,找到对应的location块,加入一行Lua脚本调用即可。
location / {
# 如果该location下存在静态资源文件,可以考虑做个判断,避免对静态资源也进行黑名单校验
# if ($request_uri ~ .*\.(html|htm|jpg|js|css)) {
# access_by_lua_file /usr/local/lua/access_limit.lua;
# }
access_by_lua_file /usr/local/lua/access_limit.lua; # 关键配置,引入Lua限流脚本
alias /usr/local/web/;
index index.html index.htm;
}
这里有个小技巧:如果你的这个location主要存放图片、CSS等静态文件,且攻击主要针对动态接口,可以用if判断(注释部分所示)来减少不必要的Redis查询,进一步提升性能。不过,对于大多数场景,直接全局启用也没问题。
接下来是重头戏:编写/usr/local/lua/access_limit.lua脚本。这个脚本负责具体的封禁逻辑,其核心思想是:在单位时间内,如果某个IP的访问次数超过阈值,则将其加入黑名单一段时间。
-- access_limit.lua
-- 目标:自动将访问过频的IP加入黑名单封禁一段时间
-- ---------- 配置区 ----------
local pool_max_idle_time = 10000 -- Redis连接池超时回收时间(毫秒)
local pool_size = 100 -- Redis连接池大小
local redis_connection_timeout = 100 -- Redis连接超时时间(毫秒)
local redis_host = "your redis host ip" -- Redis服务器地址
local redis_port = "your redis port" -- Redis端口
local redis_auth = "your redis auth password" -- Redis认证密码
local ip_block_time = 120 -- IP被封禁的时长(秒)
local ip_time_out = 1 -- 统计访问频率的时间窗口(秒)
local ip_max_count = 3 -- 时间窗口内允许的最大访问次数
-- ---------- 配置结束 ----------
-- 记录错误日志
local function errlog(msg, ex)
ngx.log(ngx.ERR, msg, ex)
end
-- 将Redis连接归还至连接池
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 connect err:", err)
return red:close()
end
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) -- 连接失败,返回500错误
end
-- 设置超时
client:set_timeout(redis_connection_timeout)
-- 优化:只有新建的连接才需要认证密码
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
-- 获取客户端真实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
local clientIp = getIp();
local incrKey = "limit:count:" .. clientIp -- 用于计数器的Key
local blockKey = "limit:block:" .. clientIp -- 用于标记是否封禁的Key
-- 检查IP是否已被封禁
local is_block, err = client:get(blockKey)
if tonumber(is_block) == 1 then
ngx.exit(ngx.HTTP_FORBIDDEN) -- 如果已被封禁,直接返回403
close_redis(client)
end
-- IP访问计数:每次访问+1,如果是首次(值为1),设置计数器过期时间
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
-- 操作完成,释放连接
close_redis(client)
这个脚本的逻辑链条非常清晰:获取IP -> 查是否已封禁 -> 统计访问次数 -> 判断是否触发封禁。所有状态都存储在Redis中,因此多台Nginx服务器可以共享同一份黑名单,实现了分布式防护。
至此,一个基于Nginx+Lua+Redis的动态IP黑名单系统就搭建完成了。回顾一下,这个方案有几个突出的优点:
1. 轻量高效: 在Nginx的访问阶段进行拦截,对后端应用服务几乎无感,性能损耗极小。
2. 集中管理: 通过Redis集中存储黑名单,任何服务器上的策略变更都能实时同步,管理起来非常方便。
3. 动态灵活: 封禁策略(如时间窗口、阈值、封禁时长)可以通过修改Lua脚本或Redis数据动态调整,无需重启服务。
这个功能可不是简单的“关门谢客”,它在多种安全防护场景下都能大显身手:
防御恶意扫描与攻击: 有效拦截那些进行密码暴力破解、SQL注入、XSS跨站脚本测试的IP源。
反爬虫与数据保护: 限制恶意爬虫高频抓取数据,既能减轻服务器负载,也能保护核心数据资产。
缓解DDoS攻击: 虽然无法应对海量分布式攻击,但对于小规模或持续性的攻击源,将其IP加入黑名单可以快速切断流量,为其他防护措施争取时间。
业务风控: 例如,限制同一IP在秒杀活动中的抢购次数,或者在投票系统中防止刷票。
基础功能实现后,还可以在此基础上做更多文章,让防护体系更智能、更健壮:
智能异常检测与自动封禁: 结合日志分析,可以建立更复杂的模型。例如,不仅检测访问频率,还分析访问路径、User-Agent等特征,自动识别并封禁有异常行为的IP。
引入白名单机制: 有黑就有白。将可信的IP(如公司出口IP、合作伙伴IP)加入白名单,确保其访问永远不会被误拦截,这对于运维和内部测试至关重要。
验证码挑战: 对于可疑但又不确定是恶意攻击的IP(例如频率略高于阈值),可以不直接封禁,而是弹出验证码进行人机验证。通过则正常访问,失败则加入黑名单。
数据统计与分析: 将黑名单的触发记录、封禁时长、IP来源等信息进行统计和分析。这些数据是优化防护策略的宝贵依据,能帮你回答诸如“攻击主要来自哪个地区?”“哪种攻击模式最常见?”等问题。
安全防护是一个持续的过程。通过不断迭代和优化IP黑名单策略,你可以为服务器和应用构筑起一道更加灵活、智能的动态防线。
菜鸟下载发布此文仅为传递信息,不代表菜鸟下载认同其观点或证实其描述。