OpenResty(Lua)+Redis实现动态封禁IP

文章目录

架构设计

通过 Nginx + Redis 的方案,确实可以将流量挡在 Nginx 和 Redis 层,从而利用nginx和redis适合高并发的特点,保护后端了服务。如果需要更高的性能和可靠性,推荐使用防火墙/WAF相关手段,将流量挡在网络层面。

  • 动态封禁逻辑:通过Lua脚本实时分析Nginx访问日志,将高频请求的IP写入Redis黑名单。
  • 自动解封机制:Redis中设置IP的TTL过期时间,到期自动释放。
  • 性能优化:使用共享内存缓存黑名单,减少Redis查询压力。

环境准备

关闭防火墙和selinux,进行时间同步。

  • 操作系统:两台CentOS 7.9(本文章使用)或其他操作系统
  • 软件依赖:OpenResty(集成Nginx+Lua)、Redis 5.0+
  • 网络权限:确保Redis端口(默认6379)可访问

主机OpenResty:

ini 复制代码
[root@localhost ~]# hostnamectl set-hostname openresty
[root@localhost ~]# bash
[root@openresty ~]# ip a | grep 192.168
    inet 192.168.226.133/24 brd 192.168.226.255 scope global noprefixroute ens33
[root@openresty ~]# hostnamectl
   Static hostname: openresty
   Pretty hostname: openresty
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 7ec089d0625e4b258d18677311cbeb31
           Boot ID: 550ce2f4975f431aaf449951924e6821
    Virtualization: vmware
  Operating System: CentOS Linux 7 (Core)
       CPE OS Name: cpe:/o:centos:centos:7
            Kernel: Linux 3.10.0-1160.el7.x86_64
      Architecture: x86-64
[root@openresty ~]# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

主机Redis:

bash 复制代码
[root@localhost ~]# hostnamectl set-hostname redis
[root@localhost ~]# bash
bash
[root@redis ~]# ip a | grep 192.168
    inet 192.168.226.134/24 brd 192.168.226.255 scope global noprefixroute ens33

[root@redis ~]# hostnamectl
   Static hostname: redis
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 7ec089d0625e4b258d18677311cbeb31
           Boot ID: 3bc6025f09f84885a928787b996fdf28
    Virtualization: vmware
  Operating System: CentOS Linux 7 (Core)
       CPE OS Name: cpe:/o:centos:centos:7
            Kernel: Linux 3.10.0-1160.el7.x86_64
      Architecture: x86-64
[root@redis ~]# cat /etc/os-release 
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

源码编辑安装OpenResty

下载

在192.168.226.133上操作

  1. 官网下载:https://openresty.org/cn/download.html

    上传压缩包至openresty主机中

  2. 使用wget下载:

bash 复制代码
[root@openresty ~]# wget https://openresty.org/download/openresty-1.25.3.2.tar.gz
--2025-03-14 23:06:24--  https://openresty.org/download/openresty-1.25.3.2.tar.gz
Resolving openresty.org (openresty.org)... 47.91.165.147
Connecting to openresty.org (openresty.org)|47.91.165.147|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 5837745 (5.6M) [application/x-gzip]
Saving to: 'openresty-1.25.3.2.tar.gz'

100%[==============================================================================================================================================================================================>] 5,837,745   4.46MB/s   in 1.2s   

2025-03-14 23:06:26 (4.46 MB/s) - 'openresty-1.25.3.2.tar.gz' saved [5837745/5837745]

# 解压
[root@openresty ~]# tar -zxvf openresty-1.25.3.2.tar.gz

安装准备依赖

bash 复制代码
[root@openresty ~]# mkdir -p ./package

# 下载依赖包并缓存到当前目录下的package目录里,这个命令方便离线缓存后拿到内网主机安装
# 将 package 目录拷贝到目标机器后,通过以下命令批量安装:rpm -ivh ./package/*.rpm --nodeps --force
# --downloadonly 会下载主包及其所有依赖的 RPM 文件,但需注意:
# 如果依赖包已安装,yum 默认不会重复下载。若需强制下载,需在未安装的环境中操作 
# 使用 --setopt=keepcache=1 可强制缓存,但需在 /etc/yum.conf 中设置 keepcache=1 
# 不同Linux发行版依赖包名可能不同(如Ubuntu用apt安装libpcre3-dev等)
[root@openresty ~]# yum install -y --downloadonly --downloaddir=./package readline-devel pcre pcre-devel openssl openssl-devel gcc curl GeoIP-devel perl

编译

bash 复制代码
[root@openresty ~]# cd openresty-1.25.3.2
[root@openresty openresty-1.25.3.2]# ll
total 104
drwxrwxr-x 46 1000 1000  4096 Jul 17  2024 bundle
-rwxrwxr-x  1 1000 1000 51486 Jul 17  2024 configure
-rw-rw-r--  1 1000 1000 22924 Jul 17  2024 COPYRIGHT
drwxrwxr-x  2 1000 1000  4096 Jul 17  2024 patches
-rw-rw-r--  1 1000 1000  4689 Jul 17  2024 README.markdown
-rw-rw-r--  1 1000 1000  8974 Jul 17  2024 README-windows.txt
drwxrwxr-x  2 1000 1000    52 Jul 17  2024 util

# 自行确认依赖包都已安装
# 运行配置脚本以准备编译过程;使用./configure --help查看所有可用参数
# 若需自定义安装路径,添加--prefix=/your/path(默认路径为/usr/local/openresty)
[root@openresty openresty-1.25.3.2]#./configure \
  --with-luajit \
  --with-http_ssl_module \
  --with-http_gzip_static_module \
  --with-http_realip_module \
  --with-http_stub_status_module
  
注释:
    --with-luajit:启用LuaJIT支持(高性能Lua引擎)。
    --with-http_ssl_module:启用HTTPS支持。
    --with-http_gzip_static_module:启用静态文件压缩。
    --with-http_realip_module:获取客户端真实IP。
    --with-http_stub_status_module:启用Nginx状态监控

安装

bash 复制代码
[root@openresty openresty-1.25.3.2]# make -j$(nproc)   # 并行编译,加速构建
[root@openresty openresty-1.25.3.2]# make install      # 安装到系统目录

配置环境变量(可选)

为方便启动,可将OpenResty的Nginx路径加入PATH修改~/.bashrc/etc/profile

bash 复制代码
[root@openresty openresty-1.25.3.2]# export PATH=/usr/local/openresty/nginx/sbin:$PATH
[root@openresty openresty-1.25.3.2]# source ~/.bashrc

配置路径解析

配置项 路径 作用说明
Nginx 根目录 /usr/local/openresty/nginx 所有组件和文件的安装根目录
Nginx 可执行文件 /usr/local/openresty/nginx/sbin/nginx 启动、停止服务的核心二进制文件
动态模块目录 /usr/local/openresty/nginx/modules 存放通过 load_module 加载的动态模块(如第三方扩展)
主配置文件 /usr/local/openresty/nginx/conf/nginx.conf 全局配置入口,建议分拆配置到 conf.d/ 目录
PID 文件 /usr/local/openresty/nginx/logs/nginx.pid 记录 Nginx 主进程的进程 ID(用于 nginx -s reload 等操作)
错误日志 /usr/local/openresty/nginx/logs/error.log 服务运行时的错误日志(故障排查优先查看此文件)
访问日志 /usr/local/openresty/nginx/logs/access.log 所有 HTTP 请求的访问日志(需定期清理或轮转)
临时文件目录 client_body_temp, proxy_temp, fastcgi_temp 处理请求时生成的临时文件(需确保磁盘空间充足和写入权限)

OpenResty 服务管理命令

bash 复制代码
# 安全启动配置-创建专用用户
[root@openresty openresty-1.25.3.2]# cd
[root@openresty ~]# useradd -M -s /sbin/nologin nginx                # 创建无登录权限的用户
[root@openresty ~]# chown -R nginx:nginx /usr/local/openresty/nginx/  # 修改目录权限
命令 参数/选项 说明
/usr/local/openresty/nginx/sbin/nginx -p /custom/path 指定 OpenResty 项目根目录(默认 /usr/local/openresty
-c nginx.conf 指定配置文件路径(默认 /usr/local/openresty/nginx/conf/nginx.conf
-s stop 强制停止服务
-s quit 优雅停止服务(等待请求处理完成)
-s reload 重载配置文件(不中断服务)
-t 检查配置文件语法是否正确
-V 显示 OpenResty 版本及编译时启用的模块(如 LuaJIT、SSL 等)

编辑 nginx.conf

修改下属配置,一般就在第2行,修改完成后保存退出

ini 复制代码
[root@openresty ~]# vim /usr/local/openresty/nginx/conf/nginx.conf
user nginx;

启动

ini 复制代码
# 直接启动
[root@openresty ~]# /usr/local/openresty/nginx/sbin/nginx
# 检查进程是否运行
[root@openresty ~]# ps aux | grep nginx
root      25779  0.0  0.0  55312  1708 ?        Ss   00:52   0:00 nginx: master process /usr/local/openresty/nginx/sbin/nginx
nginx     25780  0.0  0.1  55736  2472 ?        S    00:52   0:00 nginx: worker process

[root@openresty ~]# ss -tnlp | grep 80
LISTEN     0      128          *:80                       *:*                   users:(("nginx",pid=25780,fd=6),("nginx",pid=25779,fd=6))


浏览器访问:http://192.168.226.133/
然后可以看到Welcome to OpenResty!的页面。

安装Redis

在192.168.226.134主机安装

redis这里直接使用yum安装了

bash 复制代码
# centos7.9的默认仓库epel只有一个3.2x版本的Redis
# Remi 仓库是一个常用的第三方仓库,提供了较新的软件版本。
[root@redis ~]# yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm

# 查看 Remi 仓库中的 Redis 版本
[root@redis ~]# yum --enablerepo=remi list redis --showduplicates
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * remi: mirrors.tuna.tsinghua.edu.cn
 * remi-safe: mirrors.tuna.tsinghua.edu.cn
 * updates: mirrors.aliyun.com
remi                                                                                                                                                                       | 3.0 kB  00:00:00     
remi-safe                                                                                                                                                                  | 3.0 kB  00:00:00     
(1/2): remi-safe/primary_db                                                                                                                                                | 2.6 MB  00:00:01     
(2/2): remi/primary_db                                                                                                                                                     | 3.7 MB  00:00:02     
Available Packages
redis.x86_64                                                                                3.2.12-2.el7                                                                                      epel
redis.x86_64                                                                                5.0.13-1.el7.remi                                                                                 remi
redis.x86_64                                                                                5.0.14-1.el7.remi                                                                                 remi
redis.x86_64                                                                                6.0.19-1.el7.remi                                                                                 remi
redis.x86_64                                                                                6.0.20-1.el7.remi                                                                                 remi
redis.x86_64                                                                                6.2.13-1.el7.remi                                                                                 remi
redis.x86_64                                                                                6.2.14-1.el7.remi                                                                                 remi
redis.x86_64                                                                                7.0.14-1.el7.remi                                                                                 remi
redis.x86_64                                                                                7.0.15-1.el7.remi                                                                                 remi
redis.x86_64                                                                                7.2.4-1.el7.remi                                                                                  remi
redis.x86_64                                                                                7.2.5-1.el7.remi                                                                                  remi

# 选择一个版本下载安装
[root@redis ~]# yum -y --enablerepo=remi install redis-5.0.14-1.el7.remi
[root@redis ~]# systemctl enable --now redis

[root@redis ~]# redis-cli -v
redis-cli 5.0.14

# 配置连接Redis的用户名和密码
# 编辑 Redis 配置文件(通常位于 /etc/redis.conf
# 找到 requirepass 字样附近插入配置并设置密码
[root@redis ~]# vim /etc/redis.conf
requirepass "GFrc3d+76Fr*Ctpt"

# 重启Redis
[root@redis ~]# systemctl restart redis
# 使用密码连接测试
[root@redis ~]# redis-cli -h 127.0.0.1 -p 6379 -a "GFrc3d+76Fr*Ctpt"
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> 

# 配置指定IP可以连接Redis 
# 编辑配置文件找到bind关键字,增加指定IP的配置
[root@redis ~]# vim /etc/redis.conf
bind 127.0.0.1 192.168.226.133 192.168.226.134

# 重启Redis,然后自行连接测试即可
[root@redis ~]# systemctl restart redis

配置Lua脚本

在192.168.226.133上操作

ini 复制代码
[root@openresty ~]# cd /usr/local/openresty/nginx/conf

# 更新nginx.conf配置
[root@openresty conf]# cat nginx.conf
user  nginx;
worker_processes  auto;

# 错误日志设置
error_log  logs/error.log  warn;
pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    # 真实IP解析设置
    real_ip_header     X-Forwarded-For;
    real_ip_recursive  on;

    # 定义日志格式
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    # Lua配置
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    lua_shared_dict ip_counters 10m;  # 用于扩展速率限制

    server {
        listen       80;
        server_name  localhost;

        # 在访问阶段执行IP检查
        access_by_lua_file conf/lua/ip_block.lua;

        location / {
            root   html;
            index  index.html index.htm;
        }

        # 封禁IP管理接口(生产环境需要添加鉴权!)
        location /manage/blacklist {
            allow 127.0.0.1;  # 只允许本地访问
            deny all;
            # 参数示例:
            # /manage/blacklist?action=add&ip=1.2.3.4
            # /manage/blacklist?action=del&ip=1.2.3.4
            content_by_lua_block {
                local redis = require "resty.redis"
                local red = redis:new()
                red:set_timeout(1000)

                local ip = ngx.var.arg_ip
                local action = ngx.var.arg_action

                if not (ip and action) then
                    ngx.say('{"code":400,"msg":"缺少参数"}')
                    return
                end

                -- 连接Redis
                local ok, err = red:connect("192.168.226.134", 6379)
                if not ok then
                    ngx.say('{"code":500,"msg":"Redis连接失败"}')
                    return
                end

                -- 认证
                local auth_ok, auth_err = red:auth("GFrc3d+76Fr*Ctpt")
                if not auth_ok then
                    ngx.say('{"code":500,"msg":"Redis认证失败"}')
                    return
                end

                red:select(15)

                -- 执行操作
                local res
                if action == "add" then
                    res, err = red:sadd("blacklist", ip)
                elseif action == "del" then
                    res, err = red:srem("blacklist", ip)
                else
                    ngx.say('{"code":400,"msg":"无效操作"}')
                    return
                end

                if err then
                    ngx.say('{"code":500,"msg":"操作失败: '..err..'"}')
                else
                    ngx.say('{"code":200,"msg":"操作成功","affected":'..res..'}')
                end

                red:close()
            }
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

创建Lua脚本(日志记录允许通过的不允许的配置,选择一个lua脚本配置即可。)

ini 复制代码
[root@openresty conf]# mkdir lua
[root@openresty conf]# cd lua/
[root@openresty lua]# cat ip_block.lua
local redis = require "resty.redis"
local cjson = require "cjson.safe"

-- 配置区域 ===========================================
local SECURITY_CONFIG = {
    redis = {
        host = "192.168.226.134",   -- Redis 服务器地址
        port = 6379,                -- Redis 端口
        password = "GFrc3d+76Fr*Ctpt", -- Redis 认证密码
        database = 15,              -- 使用的数据库编号
        timeout = 1000              -- 连接超时时间(毫秒)
    },
    rate_limit = {
        window = 5,                 -- 统计时间窗口(秒)
        threshold = 10,             -- 请求阈值(次数/窗口)
        ban_duration = 60           -- 封禁时长(秒)
    },
    log = {
        path = "/usr/local/openresty/nginx/logs/lua_combined.log" -- 日志文件路径
    }
}
-- 结束配置区域 =======================================

-- 获取客户端 IP
local function get_client_ip()
    local headers = ngx.req.get_headers()
    return headers["X-Real-IP"] or 
           (headers["X-Forwarded-For"] and headers["X-Forwarded-For"]:match("([^,]+)")) or
           ngx.var.remote_addr
end

-- 记录日志
local function log_event(event_type, client_ip, reason)
    local log_entry = {
        timestamp = ngx.localtime(),
        event = event_type,
        client_ip = client_ip,
        reason = reason,
        request = {
            method = ngx.req.get_method(),
            uri = ngx.var.request_uri,
            args = ngx.req.get_uri_args()
        }
    }

    local json_str, err = cjson.encode(log_entry)
    if not json_str then
        ngx.log(ngx.ERR, "JSON 编码失败: ", err)
        return
    end

    -- 异步写入日志
    local ok, err = ngx.timer.at(0, function(premature, path, entry)
        if premature then return end
        local file = io.open(path, "a")
        if file then
            file:write(entry, "\n")
            file:close()
        else
            ngx.log(ngx.ERR, "无法打开日志文件: ", path)
        end
    end, SECURITY_CONFIG.log.path, json_str)

    if not ok then
        ngx.log(ngx.ERR, "无法创建日志定时器: ", err)
    end
end

-- 主逻辑
local client_ip = get_client_ip()
local red = redis:new()
red:set_timeout(SECURITY_CONFIG.redis.timeout)

-- 连接 Redis
local ok, err = red:connect(SECURITY_CONFIG.redis.host, SECURITY_CONFIG.redis.port)
if not ok then
    ngx.log(ngx.ERR, "Redis 连接失败: ", err)
    return ngx.exit(500)
end

-- 认证
local auth_ok, auth_err = red:auth(SECURITY_CONFIG.redis.password)
if not auth_ok then
    ngx.log(ngx.ERR, "Redis 认证失败: ", auth_err)
    return ngx.exit(500)
end

-- 选择数据库
red:select(SECURITY_CONFIG.redis.database)

-- 检查白名单
local is_whitelisted, err = red:sismember("whitelist", client_ip)
if is_whitelisted == 1 then
    log_event("WHITELIST_BYPASS", client_ip, "IP 在白名单中")
    red:set_keepalive(10000, 100)  -- 释放连接到连接池
    return  -- 白名单直接放行
end

-- 检查黑名单
local is_blacklisted, err = red:sismember("blacklist", client_ip)
if is_blacklisted == 1 then
    log_event("BLACKLIST_BLOCK", client_ip, "IP 在黑名单中")
    red:set_keepalive(10000, 100)  -- 释放连接到连接池
    return ngx.exit(403)  -- 返回 403 禁止访问
end

-- 检查请求频率
local counter_key = "rate_limit:" .. client_ip
local request_count, err = red:incr(counter_key)
if not request_count then
    ngx.log(ngx.ERR, "Redis 计数器增加失败: ", err)
    return ngx.exit(500)
end

-- 如果是第一次访问,设置过期时间
if request_count == 1 then
    red:expire(counter_key, SECURITY_CONFIG.rate_limit.window)
end

-- 触发封禁条件
if request_count > SECURITY_CONFIG.rate_limit.threshold then
    local ok, err = red:sadd("blacklist", client_ip)
    if not ok then
        ngx.log(ngx.ERR, "添加黑名单失败: ", err)
        return ngx.exit(500)
    end
    red:expire("blacklist", SECURITY_CONFIG.rate_limit.ban_duration)
    log_event("RATE_LIMIT_BLOCK", client_ip, "请求频率超限")
    red:set_keepalive(10000, 100)  -- 释放连接到连接池
    return ngx.exit(429)  -- 返回 429 请求过多
end

-- 正常请求
log_event("ALLOWED_REQUEST", client_ip, "请求通过")
red:set_keepalive(10000, 100)  -- 释放连接到连接池

日志仅记录不允许的配置

ini 复制代码
local redis = require "resty.redis"
local cjson = require "cjson.safe"

-- 配置区域 ===========================================
local SECURITY_CONFIG = {
    redis = {
        host = "192.168.226.134",   -- Redis 服务器地址
        port = 6379,                -- Redis 端口
        password = "GFrc3d+76Fr*Ctpt", -- Redis 认证密码
        database = 15,              -- 使用的数据库编号
        timeout = 1000              -- 连接超时时间(毫秒)
    },
    rate_limit = {
        window = 5,                 -- 统计时间窗口(秒)
        threshold = 10,             -- 请求阈值(次数/窗口)
        ban_duration = 60           -- 封禁时长(秒)
    },
    log = {
        path = "/usr/local/openresty/nginx/logs/lua_combined.log" -- 日志文件路径
    }
}
-- 结束配置区域 =======================================

-- 获取客户端 IP
local function get_client_ip()
    local headers = ngx.req.get_headers()
    return headers["X-Real-IP"] or 
           (headers["X-Forwarded-For"] and headers["X-Forwarded-For"]:match("([^,]+)")) or
           ngx.var.remote_addr
end

-- 记录日志
local function log_event(event_type, client_ip, reason)
    local log_entry = {
        timestamp = ngx.localtime(),
        event = event_type,
        client_ip = client_ip,
        reason = reason,
        request = {
            method = ngx.req.get_method(),
            uri = ngx.var.request_uri,
            args = ngx.req.get_uri_args()
        }
    }

    local json_str, err = cjson.encode(log_entry)
    if not json_str then
        ngx.log(ngx.ERR, "JSON 编码失败: ", err)
        return
    end

    -- 异步写入日志
    local ok, err = ngx.timer.at(0, function(premature, path, entry)
        if premature then return end
        local file = io.open(path, "a")
        if file then
            file:write(entry, "\n")
            file:close()
        else
            ngx.log(ngx.ERR, "无法打开日志文件: ", path)
        end
    end, SECURITY_CONFIG.log.path, json_str)

    if not ok then
        ngx.log(ngx.ERR, "无法创建日志定时器: ", err)
    end
end

-- 主逻辑
local client_ip = get_client_ip()
local red = redis:new()
red:set_timeout(SECURITY_CONFIG.redis.timeout)

-- 连接 Redis
local ok, err = red:connect(SECURITY_CONFIG.redis.host, SECURITY_CONFIG.redis.port)
if not ok then
    ngx.log(ngx.ERR, "Redis 连接失败: ", err)
    return ngx.exit(500)
end

-- 认证
local auth_ok, auth_err = red:auth(SECURITY_CONFIG.redis.password)
if not auth_ok then
    ngx.log(ngx.ERR, "Redis 认证失败: ", auth_err)
    return ngx.exit(500)
end

-- 选择数据库
red:select(SECURITY_CONFIG.redis.database)

-- 检查白名单
local is_whitelisted, err = red:sismember("whitelist", client_ip)
if is_whitelisted == 1 then
    red:set_keepalive(10000, 100)  -- 释放连接到连接池
    return  -- 白名单直接放行
end

-- 检查黑名单
local is_blacklisted, err = red:sismember("blacklist", client_ip)
if is_blacklisted == 1 then
    log_event("BLACKLIST_BLOCK", client_ip, "IP 在黑名单中")
    red:set_keepalive(10000, 100)  -- 释放连接到连接池
    return ngx.exit(403)  -- 返回 403 禁止访问
end

-- 检查请求频率
local counter_key = "rate_limit:" .. client_ip
local request_count, err = red:incr(counter_key)
if not request_count then
    ngx.log(ngx.ERR, "Redis 计数器增加失败: ", err)
    return ngx.exit(500)
end

-- 如果是第一次访问,设置过期时间
if request_count == 1 then
    red:expire(counter_key, SECURITY_CONFIG.rate_limit.window)
end

-- 触发封禁条件
if request_count > SECURITY_CONFIG.rate_limit.threshold then
    local ok, err = red:sadd("blacklist", client_ip)
    if not ok then
        ngx.log(ngx.ERR, "添加黑名单失败: ", err)
        return ngx.exit(500)
    end
    red:expire("blacklist", SECURITY_CONFIG.rate_limit.ban_duration)
    log_event("RATE_LIMIT_BLOCK", client_ip, "请求频率超限")
    red:set_keepalive(10000, 100)  -- 释放连接到连接池
    return ngx.exit(429)  -- 返回 429 请求过多
end

-- 正常请求,不记录日志
red:set_keepalive(10000, 100)  -- 释放连接到连接池

热加载配置

bash 复制代码
[root@openresty lua]# nginx -t
nginx: the configuration file /usr/local/openresty/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/openresty/nginx/conf/nginx.conf test is successful
[root@openresty lua]# nginx -s reload
# 修改目录权限
[root@openresty lua]# chown -R nginx:nginx /usr/local/openresty/nginx/
[root@openresty lua]# chmod -R 755 /usr/local/openresty/nginx/html

附白名单操作:

如果需要加白处理,需要手动在Redis中插入

ini 复制代码
SADD whitelist 192.168.226.134

测试

准备测试工具

一般yum 官方仓库中未提供 wrk 的预编译包。

下附wrk安装方法:

ini 复制代码
# 安装依赖
yum install -y git make gcc openssl-devel
# 克隆源码并编译
git clone https://github.com/wg/wrk.git
cd wrk
make
# 安装到系统路径
cp wrk /usr/local/bin
# 验证安装,输出版本信息即表示成功
wrk --version

测试封禁逻辑

使用 ab 进行压力测试

运行以下命令,模拟高频率请求:

bash 复制代码
[root@openresty ~]# curl http://localhost/

[root@openresty ~]# ab -n 100 -c 10 http://192.168.226.133/
    参数说明:
    - `-n 100`:总请求数为 100。
    - `-c 10`:并发请求数为 10。
    
[root@openresty ~]# curl http://localhost/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>openresty</center>
</body>
</html>

观察 Nginx 日志和 Lua 日志,检查是否触发封禁逻辑:

如果封禁逻辑生效,你会看到类似以下的日志:

ini 复制代码
[root@openresty ~]# tail -f /usr/local/openresty/nginx/logs/lua_combined.log  # 我使用的lua配置仅记录黑名单的访问,因此就看到了拒绝的日志
{"timestamp":"2025-03-15 20:02:24","event":"RATE_LIMIT_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.1","reason":"请求频率超限"}
{"timestamp":"2025-03-15 20:02:24","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.1","reason":"IP 在黑名单中"}
{"timestamp":"2025-03-15 20:02:24","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.1","reason":"IP 在黑名单中"}
{"timestamp":"2025-03-15 20:02:24","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.1","reason":"IP 在黑名单中"}
{"timestamp":"2025-03-15 20:02:25","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.1","reason":"IP 在黑名单中"}
{"timestamp":"2025-03-15 20:03:05","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"IP 在黑名单中"}
{"timestamp":"2025-03-15 20:03:05","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"IP 在黑名单中"}
{"timestamp":"2025-03-15 20:03:05","event":"RATE_LIMIT_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"请求频率超限"}
{"timestamp":"2025-03-15 20:03:05","event":"RATE_LIMIT_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"请求频率超限"}
{"timestamp":"2025-03-15 20:03:05","event":"RATE_LIMIT_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"请求频率超限"}
{"timestamp":"2025-03-15 20:03:05","event":"RATE_LIMIT_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"请求频率超限"}
{"timestamp":"2025-03-15 20:03:05","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"IP 在黑名单中"}
{"timestamp":"2025-03-15 20:03:05","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"IP 在黑名单中"}
{"timestamp":"2025-03-15 20:03:05","event":"BLACKLIST_BLOCK","request":{"args":{},"method":"GET","uri":"\/"},"client_ip":"192.168.226.134","reason":"IP 在黑名单中"}

删除版本信息

1.通过nginx.conf配置

关闭全局版本信息显示

当访问浏览器这个服务的一个不存在的页面时,可以看到

ini 复制代码
404 Not Found
openresty/1.25.3.2

或使用

ini 复制代码
[root@openresty ~]# curl -I http:192.168.226.133
HTTP/1.1 200 OK
Server: openresty/1.25.3.2
Date: Fri, 14 Mar 2025 17:29:43 GMT
Content-Type: text/html
Content-Length: 116103
Last-Modified: Fri, 14 Mar 2025 15:48:58 GMT
Connection: keep-alive
ETag: "67d44fea-1c587"
Accept-Ranges: bytes

要关闭版本信息这个功能,需要修改nginx配置

vim /usr/local/openresty/nginx/conf/nginx.conf

在http模块里配置是全局生效,在特定的server块里配置是特定server块里的生效,这里我设置全局生效。

ini 复制代码
[root@openresty ~]#  vim /usr/local/openresty/nginx/conf/nginx.conf
http {
    ...
    server_tokens off;  # 隐藏 Nginx 版本信息
    ...
}

热加载配置

ini 复制代码
[root@openresty ~]# nginx -s reload

重新访问一个404页面验证

ini 复制代码
404 Not Found
openresty

然后使用curl

ini 复制代码
[root@openresty ~]# curl -I http://192.168.226.133
HTTP/1.1 200 OK
Server: openresty
Date: Fri, 14 Mar 2025 17:32:04 GMT
Content-Type: text/html
Content-Length: 116103
Last-Modified: Fri, 14 Mar 2025 15:48:58 GMT
Connection: keep-alive
ETag: "67d44fea-1c587"
Accept-Ranges: bytes

通过配置server_tokens off;参数只能隐藏1.25.3.2d而不能隐藏openresty`信息。

2.修改编译选项

通过配置server_tokens off;但是发现还会有openresty字样

下述为彻底删除版本信息方式:

ini 复制代码
[root@openresty ~]# ll
total 5712
-rw-------. 1 root  root     1259 Feb  7 08:04 anaconda-ks.cfg
drwxrwxr-x  6 nginx nginx     159 Mar 14 23:48 openresty-1.25.3.2
-rw-r--r--  1 root  root  5837745 Jul 17  2024 openresty-1.25.3.2.tar.gz
drwxr-xr-x  2 root  root     4096 Mar 14 23:27 package
[root@openresty ~]# cd openresty-1.25.3.2
[root@openresty openresty-1.25.3.2]# ll
total 116
drwxrwxr-x 47 nginx nginx  4096 Mar 14 23:47 build
drwxrwxr-x 46 nginx nginx  4096 Jul 17  2024 bundle
-rwxrwxr-x  1 nginx nginx 51486 Jul 17  2024 configure
-rw-rw-r--  1 nginx nginx 22924 Jul 17  2024 COPYRIGHT
-rw-r--r--  1 root  root   6005 Mar 14 23:48 Makefile
drwxrwxr-x  2 nginx nginx  4096 Jul 17  2024 patches
-rw-rw-r--  1 nginx nginx  4689 Jul 17  2024 README.markdown
-rw-rw-r--  1 nginx nginx  8974 Jul 17  2024 README-windows.txt
drwxrwxr-x  2 nginx nginx    52 Jul 17  2024 util

# 修改源码中的版本信息:
[root@openresty openresty-1.25.3.2]# cd bundle/nginx-1.25.3/src/core/
[root@openresty core]# cat  nginx.h

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#ifndef _NGINX_H_INCLUDED_
#define _NGINX_H_INCLUDED_


#define nginx_version      1025003
#define NGINX_VERSION      "1.25.3"
#define NGINX_VER          "openresty/" NGINX_VERSION ".2"

#ifdef NGX_BUILD
#define NGINX_VER_BUILD    NGINX_VER " (" NGX_BUILD ")"
#else
#define NGINX_VER_BUILD    NGINX_VER
#endif

#define NGINX_VAR          "NGINX"
#define NGX_OLDPID_EXT     ".oldbin"


#endif /* _NGINX_H_INCLUDED_ */

然后自定义修改为:

ini 复制代码
[root@openresty core]# cat nginx.h

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#ifndef _NGINX_H_INCLUDED_
#define _NGINX_H_INCLUDED_


#define nginx_version      1025003
#define NGINX_VERSION      "I'm a walk-on"
#define NGINX_VER          "Hello! " NGINX_VERSION "_______110"

#ifdef NGX_BUILD
#define NGINX_VER_BUILD    NGINX_VER " (" NGX_BUILD ")"
#else
#define NGINX_VER_BUILD    NGINX_VER
#endif

#define NGINX_VAR          "NGINX"
#define NGX_OLDPID_EXT     ".oldbin"


#endif /* _NGINX_H_INCLUDED_ */

再次编译并安装

bash 复制代码
[root@openresty core]# cd 
[root@openresty ~]# ll
total 5712
-rw-------. 1 root  root     1259 Feb  7 08:04 anaconda-ks.cfg
drwxrwxr-x  6 nginx nginx     159 Mar 14 23:48 openresty-1.25.3.2
-rw-r--r--  1 root  root  5837745 Jul 17  2024 openresty-1.25.3.2.tar.gz
drwxr-xr-x  2 root  root     4096 Mar 14 23:27 package
[root@openresty ~]# cd openresty-1.25.3.2
[root@openresty openresty-1.25.3.2]# ll
total 116
drwxrwxr-x 47 nginx nginx  4096 Mar 14 23:47 build
drwxrwxr-x 46 nginx nginx  4096 Jul 17  2024 bundle
-rwxrwxr-x  1 nginx nginx 51486 Jul 17  2024 configure
-rw-rw-r--  1 nginx nginx 22924 Jul 17  2024 COPYRIGHT
-rw-r--r--  1 root  root   6005 Mar 14 23:48 Makefile
drwxrwxr-x  2 nginx nginx  4096 Jul 17  2024 patches
-rw-rw-r--  1 nginx nginx  4689 Jul 17  2024 README.markdown
-rw-rw-r--  1 nginx nginx  8974 Jul 17  2024 README-windows.txt
drwxrwxr-x  2 nginx nginx    52 Jul 17  2024 util

# 编译
[root@openresty openresty-1.25.3.2]#./configure \
  --with-luajit \
  --with-http_ssl_module \
  --with-http_gzip_static_module \
  --with-http_realip_module \
  --with-http_stub_status_module
  
# 安装
[root@openresty openresty-1.25.3.2]# make -j$(nproc)   # 并行编译,加速构建
[root@openresty openresty-1.25.3.2]# make install      # 安装到系统目录
# 进行平滑升级
# 查看旧进程ID
[root@openresty openresty-1.25.3.2]# ps aux | grep nginx
root      33968  0.0  0.0  55312  1704 ?        Ss   01:37   0:00 nginx: master process nginx
nginx     33969  0.0  0.1  55736  2712 ?        S    01:37   0:00 nginx: worker process
root      51298  0.0  0.0 112812   980 pts/0    S+   01:43   0:00 grep --color=auto nginx
# 验证配置文件无错误
[root@openresty openresty-1.25.3.2]# /usr/local/openresty/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/openresty/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/openresty/nginx/conf/nginx.conf test is successful
# 热加载平滑升级
[root@openresty openresty-1.25.3.2]# /usr/local/openresty/nginx/sbin/nginx -s reload
# 查看新进程
[root@openresty openresty-1.25.3.2]# ps aux | grep nginx
root      33968  0.0  0.1  55448  3628 ?        Ss   01:37   0:00 nginx: master process nginx
nginx     51300  0.0  0.1  55884  2656 ?        S    01:43   0:00 nginx: worker process

# 此步骤利用平滑升级的思路,重新编译安装了服务,去除了版本信息。

清除编译安装的OpenResty

bash 复制代码
# 停止 OpenResty 服务
[root@openresty ~]# /usr/local/openresty/nginx/sbin/nginx -s stop
# 删除 OpenResty 安装目录
[root@openresty ~]# rm -rf /usr/local/openresty
# 删除 OpenResty 的符号链接
[root@openresty ~]# rm -f /usr/local/bin/openresty

# 清理环境变量,删除之前定义的变量export PATH=/usr/local/openresty/nginx/sbin:$PATH
vi ~/.bash_profile

export PATH
# 删除后保存退出

# 清理编译源码目录
[root@openresty ~]# rm -rf ./openresty-1.25.3.2
# 检查是否清理干净
[root@openresty ~]# which openresty
[root@openresty ~]# which nginx
相关推荐
CodexDave4 小时前
MySQL事务隔离级别与MVCC机制解析
前端·数据库·mysql·nginx·性能优化·负载均衡
爬也要爬着前进6 小时前
redis主从搭建
数据库·redis·缓存
用户095367515838 小时前
在系统的学习 redis 前的疑惑
redis·go
吴声子夜歌11 小时前
Redis 3.x——集群故障转移
java·数据库·redis·集群
明豆12 小时前
Redis 分布式缓存生产实战
redis·分布式·缓存
海兰13 小时前
【高速缓存】RedisVL 指南:从向量搜索到 AI 应用落地
人工智能·redis
weixin_7275356213 小时前
双Token认证体系深度拆解:Spring Security + JWT + Redis
redis·spring·wpf
William Dawson14 小时前
【华为云 MRS Redis 安全集群 Kerberos 认证接入实战(Spring Boot \+ Jedis)】
redis·安全·华为云
zhouhui00115 小时前
AI帮我写了个Spring Boot校验,线上漏掉了这组边界条件
java·spring boot·redis·ai编程
chexus15 小时前
20. 深入 Nginx 信号处理
linux·c语言·网络·nginx