社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  NGINX

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

鸭哥聊Java • 1 月前 • 65 次点击  

今天我们来聊聊如何通过 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
 
65 次点击