Py学习  »  NGINX

简简单单,用 Nginx 实现动态封禁IP。。。

鸭哥聊Java • 8 月前 • 256 次点击  

今天我们来聊聊如何通过 Nginx 实现一个动态的 IP 黑名单,阻止恶意用户或者爬虫访问你的服务器。

服务器正面临着越来越多不怀好意的访问请求,而你却希望能够优雅地应对——不直接拒绝所有流量,而是巧妙地在后台设置一个自动化的 IP 封禁机制。这一切,我们都可以通过结合 Nginx、Lua 和 Redis 来实现。

首先,我们得明白,封禁 IP 其实是我们防御爬虫、暴力破解、DDoS 攻击等恶意行为的一个重要手段。

很多人可能会问,为什么我们不直接使用操作系统层面的防火墙(如 iptables)来拦截这些 IP 呢?老实说,这样的方法虽然有效,但每次修改配置都得手动重启防火墙,操作起来比较繁琐。而且这种方式也比较死板,灵活性不高,特别是在处理动态变化的黑名单时,效果不够理想。

与之相比,基于 Nginx + Lua + Redis 的方案,提供了更加灵活和高效的方式,能够在 Web 服务器层面直接对请求进行控制,自动动态地更新和管理 IP 黑名单。而且,Redis 的引入,使得黑名单的数据可以在多台服务器之间共享,非常适合分布式环境。

在我们设计这个系统时,主要的工作是基于以下几点需求:

  1. 动态封禁:我们不希望每次封禁一个 IP 都需要手动修改配置,而是希望通过程序自动识别恶意行为并封禁 IP。
  2. 定时失效:我们要能控制封禁 IP 的时间,确保封禁过期后,IP 能够恢复访问。
  3. 性能:我们希望这个方案能在大规模并发的情况下,依然能够保证性能,不能对业务产生负面影响。

为了解决这些问题,我们决定采用 Nginx + Lua + Redis 的架构。具体来说,Nginx 会通过 Lua 脚本来处理每个请求,判断请求 IP 是否在 Redis 中的黑名单里。

如果是,就拒绝服务;如果不是,则继续正常响应。同时,Redis 用于存储封禁记录、访问频次等数据,提供跨服务器的共享机制。

首先,我们需要在 Nginx 的配置文件中引入 Lua 脚本。假设我们已经安装了 OpenResty 版本的 Nginx,它自带了 Lua 模块,可以方便地运行 Lua 代码。

在 Nginx 配置文件中,找到你需要添加限制的 location 配置,然后在其中加入 access_by_lua_file 指令,指向我们的 Lua 脚本文件。这个文件的路径可能是 /usr/local/lua/access_limit.lua,根据你的实际路径调整。

location / {
access_by_lua_file /usr/local/lua/access_limit.lua; # 添加 Lua 脚本进行 IP 封禁
alias /usr/local/web/;
index index.html index.htm;
}

接下来,我们来写一下 Lua 脚本的核心部分。这个脚本的作用主要包括以下几个方面:

  • 获取请求的 IP 地址。
  • 检查该 IP 是否被封禁。
  • 判断该 IP 的访问频次,如果超过阈值,就封禁该 IP。
  • 利用 Redis 存储和共享封禁信息。
-- 连接池参数配置
local pool_max_idle_time = 10000  -- 连接池超时回收时间,单位毫秒
local pool_size = 100            -- 连接池大小
local redis_connection_timeout = 100  -- Redis 连接超时时间
local redis_host = "your_redis_host"  -- Redis 主机 IP
local redis_port = "your_redis_port"  -- Redis 端口
local redis_auth = "your_redis_auth"  -- Redis 密码(可选)
local ip_block_time = 120         -- 封禁时间(秒)
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 connection error:", 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)
end
client:set_timeout(redis_connection_timeout)

-- 如果是第一次连接 Redis,则需要进行认证
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()

-- Redis 键值:IP 访问计数与封禁标记
local incrKey = "limit:count:"..clientIp
local blockKey = "limit:block:"..clientIp

-- 检查 IP 是否被封禁
local is_block, err = client:get(blockKey)
if tonumber(is_block) == 1 then
    ngx.exit(ngx.HTTP_FORBIDDEN)  -- 封禁时返回 403 错误
    close_redis(client)
end

-- 统计访问频次
local ip_count, err = client:incr(incrKey)
if tonumber(ip_count) == 1 then
    client:expire(incrKey, ip_time_out)  -- 设置过期时间
end

-- 如果访问超过限制,则封禁该 IP
if tonumber(ip_count) > tonumber(ip_max_count) then
    client:set(blockKey, 1)
    client:expire(blockKey, ip_block_time)  -- 设置封禁时间
end

close_redis(client)

解释代码

  1. 获取 IP 地址:我们通过 ngx.req.get_headers() 获取请求头中的 X-Real-IPx_forwarded_for 字段,若这些字段不存在,则默认取 ngx.var.remote_addr 获取客户端的真实 IP。

  2. 连接 Redis:使用 resty.redis 模块连接 Redis 服务,所有 IP 黑名单信息都存储在 Redis 中。连接时,首先检查是否需要进行认证。

  3. 访问频次统计:通过 incr 指令统计每个 IP 的访问次数,并设置过期时间。若访问次数超过了预定的最大值(ip_max_count),该 IP 将被加入封禁列表,不能继续访问。

  4. 封禁 IP:若 IP 被封禁,则返回 403 错误,阻止该 IP 继续访问。

通过上述配置和 Lua 脚本,我们就实现了一个灵活的动态封禁 IP 机制。Nginx 在前端处理请求,Lua 脚本在后端判断访问是否合规,而 Redis 则作为缓存和共享黑名单的存储介质。这个方案不仅配置简单,而且对性能影响较小,能够高效地阻挡恶意访问。

当你遇到爬虫频繁抓取数据、暴力破解密码,或者遭受 DDoS 攻击时,这个系统将帮你轻松应对。通过定制黑名单、自动封禁和失效时间等功能,你可以更好地保护你的服务器,免受恶意行为的侵扰。

对编程、职场感兴趣的同学,可以链接我,微信:yagebug  拉你进入“程序员交流群”。
🔥鸭哥私藏精品 热门推荐🔥

鸭哥作为一名老码农,整理了全网最全《Java高级架构师资料合集》
资料包含了《IDEA视频教程》《最全Java面试题库》、最全项目实战源码及视频》及《毕业设计系统源码》总量高达 650GB 。全部免费领取!全面满足各个阶段程序员的学习需求。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/176013
 
256 次点击