Nginx+Lua+Redis实现动态封禁IP的几种方法

 更新时间:2026年09月30日 09:38:45   作者:❀͜͡傀儡师  
Nginx+Lua+Redis搭建动态IP黑名单,实现自动封禁与到期自动失效,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、需求背景

为了封禁某些爬虫或者恶意用户对服务器的请求,我们需要建立一个动态的IP黑名单。对于黑名单中的IP,将拒绝提供服务,并且可以设置封禁失效时间。

二、环境准备

  • Linux版本:CentOS 7 / Ubuntu等
  • Redis版本:5.0.5+
  • Nginx版本:OpenResty(内置Lua支持)

三、设计方案对比

实现方式优点缺点
iptables简单直接,物理层拦截需要手动操作,不灵活
Nginx+Lua+Redis动态封禁,分布式共享,自动失效需学习Lua脚本
应用层代码实现简单,易于维护代码臃肿,高并发影响性能

选型结论:采用 Nginx + Lua + Redis 架构实现IP黑名单功能

架构图

客户端请求 → Nginx(Lua脚本) → Redis(检查黑名单/计数) → 后端服务
                     ↓
              封禁IP返回403

四、配置Nginx

1. 修改nginx.conf

在需要进行限制的server的location中添加配置:

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; # 根据规则进行限流
    alias /usr/local/web/;
    index index.html index.htm;
}

五、Lua脚本实现

1. 创建脚本文件

路径:/usr/local/lua/access_limit.lua

-- 自动将访问频次过高的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)

六、Redis数据结构说明

Key格式用途过期时间
limit:count:IP地址记录IP在时间段内的访问次数ip_time_out(1秒)
limit:block:IP地址封禁标识,值为1表示封禁ip_block_time(120秒)

七、工作流程图

开始
  ↓
获取客户端IP
  ↓
连接Redis
  ↓
检查blockKey是否存在?
  ├─ 是 → 返回403禁止访问
  └─ 否 → 继续处理
        ↓
  对incrKey执行incr操作
        ↓
  如果是第一次访问,设置过期时间
        ↓
  检查访问次数是否超过阈值?
        ├─ 是 → 设置blockKey封禁标识
        └─ 否 → 正常访问
              ↓
        释放Redis连接
              ↓
          结束

八、高级功能扩展

1. 白名单机制

-- 在检查黑名单之前添加白名单判断
local white_list = {"127.0.0.1", "192.168.1.*"}

local function isWhiteList(ip)
    for _, value in ipairs(white_list) do
        if value == ip or ngx.re.match(ip, value:gsub("%*", ".*")) then
            return true
        end
    end
    return false
end

if isWhiteList(cliendIp) then
    close_redis(client)
    return -- 白名单直接放行
end

2. 验证码验证

对于频繁访问的IP,可以重定向到验证码页面:

if tonumber(ip_count) > tonumber(ip_max_count) then
    if tonumber(ip_count) < tonumber(ip_max_count) * 2 then
        -- 第一次超限,要求验证码
        ngx.redirect("/captcha.html")
    else
        -- 多次超限,直接封禁
        client:set(blockKey, 1)
        client:expire(blockKey, ip_block_time)
    end
end

3. 异常检测自动封禁

结合访问日志分析,可实现更智能的封禁策略:

  • 短时间内404错误过多
  • 请求路径异常(扫描行为)
  • User-Agent特征匹配

九、总结

方案优势

  1. 配置简单轻量:对服务器性能影响小
  2. 分布式共享:多台服务器通过共享Redis实例,实现黑名单统一管理
  3. 动态配置:可手工或通过自动化方式设置Redis中的黑名单
  4. 自动失效:封禁时间到期自动解除,无需人工干预

应用场景

  • 防止恶意访问(暴 力 破 解、SQL注入等)
  • 防止爬虫和数据滥用
  • 缓解DDoS攻击
  • 限制访问频率

优化建议

  1. 连接池调优:根据并发量调整pool_size和pool_max_idle_time
  2. Redis高可用:使用Redis哨兵或集群模式
  3. 监控告警:对封禁情况进行统计分析,及时调整策略

到此这篇关于Nginx+Lua+Redis实现动态封禁IP的几种方法的文章就介绍到这了,更多相关Nginx动态封禁IP内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论