聊一个我最近处理的技术难题——如何用 Nginx 实现动态封禁 IP,防止一些讨厌的爬虫或者恶意用户对你的网站进行恶意攻击。
想必大家也遇到过那些爬虫一遍又一遍疯狂请求服务器,吃掉了大量的带宽资源,或者有些恶意用户尝试用暴力破解方式登陆,着实让人头疼。
幸好,Nginx 和 Lua 结合 Redis,给我们提供了一个既高效又灵活的方案。
1. 背景与需求
有时候,你的服务器会遭遇一些恶意的攻击,常见的就是爬虫、暴力破解、SQL 注入,甚至 DDoS 攻击。
这些行为不仅增加了服务器负担,还可能导致资源浪费,甚至直接造成服务中断。解决这些问题的一种方式就是通过 IP 封禁,尤其是对于那些恶意访问频繁的 IP 地址。封禁的策略应该是动态的,可以随时添加和移除 IP,同时还要控制封禁的时长,以避免误伤正常用户。
我们希望通过以下几个步骤来实现这个目标:
封禁爬虫与恶意用户请求:识别并封禁那些频繁请求且行为恶意的 IP 地址。 建立动态 IP 黑名单:这个黑名单需要能动态更新,支持实时封禁。 封禁失效时间设置:封禁的 IP 不可能永远存在黑名单中,我们需要设置一个失效时间,自动解封。
2. 方案设计
解决这个问题的方案有好几种,每种方法都有其优缺点。
我们可以从操作系统、Web 服务器或应用层面入手,选择适合的技术栈。下面我们分析一下每种方案。
方法 1:操作系统层面的拦截(iptables)
iptables 是 Linux 系统自带的防火墙工具,通过它可以轻松封禁 IP。不过,这种方式的缺点是操作繁琐且不灵活。每当需要动态封禁某个 IP 时,我们得手动去操作,不太适合自动化管理。而且,这样的封禁是全局生效的,一旦添加了黑名单中的 IP,它就完全无法访问服务器,除非手动移除。
方法 2:Web 服务器层面的拦截(Nginx + Lua)
这种方法是我们今天的重点,利用 Nginx 的 Lua 模块配合 Redis,可以非常灵活地动态封禁 IP。Nginx 负责处理请求,Lua 脚本在请求到来时判断 IP 是否需要封禁,而 Redis 则用来存储 IP 黑名单和封禁时长。
这个方法的优点是:
动态封禁:我们可以设置一个超时机制,自动解封被封禁的 IP。 分布式管理:通过 Redis 存储黑名单,可以在多台服务器间共享数据,方便管理。 灵活性:可以对封禁策略进行动态调整,甚至支持高级的 IP 限制和行为监控。
缺点是:我们需要了解 Nginx 配置、Lua 脚本编写以及 Redis 的使用。
方法 3:应用层面的拦截(代码实现)
这种方法通过在应用层直接判断 IP 是否在黑名单中来控制访问。实现起来简单直观,但性能不如前两者。每当一个请求到来时,代码会去查询黑名单,这对于高并发场景可能会带来一定的性能压力。
3. Nginx + Lua + Redis 方案实现
接下来,我们深入讲解如何通过 Nginx 配合 Lua 脚本以及 Redis 实现动态 IP 封禁。首先,我们需要确保系统已经安装了 Lua 和 Redis,并且 Nginx 已经配置了 Lua 模块。
Nginx 配置
首先,在 Nginx 配置文件中,我们需要启用 Lua 模块,并且指定 Lua 脚本来处理访问控制。修改 nginx.conf
配置文件,在需要处理请求的 location
块中添加 Lua 脚本调用。
http {
lua_shared_dict limit_req_zone 10m; # 用来存放 Redis 连接池
server {
listen 80;
location / {
set $limit 0;
access_by_lua_file /etc/nginx/lua/access_limit.lua; # 引用 Lua 脚本
}
}
}
Lua 脚本实现(access_limit.lua)
接下来我们实现 access_limit.lua
脚本,主要完成以下几个任务:
连接 Redis。 获取请求中的客户端 IP 地址。 判断该 IP 是否在黑名单中。 如果不在黑名单中,则记录访问次数;如果在黑名单中,则拒绝请求。
local redis = require "resty.redis" -- 引入 Redis 库
local red = redis:new() -- 创建 Redis 对象
red:set_timeout(1000) -- 设置连接超时时间为 1 秒
-- 连接到 Redis 服务器
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "failed to connect to Redis: ", err)
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 获取客户端 IP 地址
local client_ip = ngx.var.remote_addr
-- 检查该 IP 是否在黑名单中
local res, err = red:get("blacklist:" .. client_ip)
if res == "1" then
ngx.log(ngx.ERR, "IP " .. client_ip .. " is blacklisted")
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- 如果 IP 不在黑名单中,则统计访问次数
local visits, err = red:get("visits:" .. client_ip)
if visits == ngx.null then
visits = 0
end
visits = visits + 1
-- 如果访问次数超过设定阈值,则加入黑名单
if visits > 10 then -- 设定的阈值为 10 次请求
red:set("blacklist:" .. client_ip, 1)
red:expire("blacklist:" .. client_ip, 3600) -- 设置封禁时长为 1 小时
end
-- 更新访问次数
red:set("visits:" .. client_ip, visits)
red:expire("visits:" .. client_ip, 60) -- 设置过期时间为 1 分钟
这个脚本通过 Redis 存储访问数据并动态判断是否需要封禁 IP。如果某个 IP 在短时间内超过了访问次数限制(比如 10 次),它就会被封禁 1 小时。
Redis 配置与连接
在这段 Lua 脚本中,我们通过 resty.redis
模块连接 Redis 服务器。Redis 在这里充当了一个缓存的角色,用来存储每个 IP 的访问次数和黑名单信息。使用 Redis 的好处是它具有高性能、易于扩展且支持分布式管理。
4. 方案总结
通过 Nginx + Lua + Redis,我们可以高效地实现 IP 封禁策略。这个方案的优点在于:
配置简单,且无需改变业务代码。 适合高并发的场景,对性能影响较小。 支持动态配置,封禁时长可以灵活调整。 Redis 支持分布式管理,可以扩展到多台服务器。
此外,Redis 还可以用于实现更多高级功能,例如 IP 访问频率限制、暴力破解防护等。
5. 扩展与高级功能
除了基本的封禁功能,Nginx + Lua + Redis 还可以扩展为更强大的安全防护系统。例如:
异常检测与自动封禁:通过分析访问日志,检测异常访问模式,自动封禁异常 IP。 白名单机制:对于一些可信的 IP,可以设置白名单,让它们绕过封禁规则。 验证码验证:对频繁访问的 IP 提供验证码验证,进一步阻止恶意访问。 数据统计与分析:记录封禁的数据,通过分析日志来优化封禁策略。
总之,Nginx 与 Lua 脚本的结合,使得动态封禁 IP 成为一个简单而灵活的解决方案,不仅能有效防止恶意访问,还可以根据需要进行扩展与优化。
对编程、职场感兴趣的同学,可以链接我,微信:coder301 拉你进入“程序员交流群”。