杨梅冲
每天在想什么呢?

0.openresty-1.25.3.2使用

环境:openresty-1.25.3.2 、redis-7.0.15

lua 脚本用途解释:

     对于 1 分钟内访问超过 300 的 IP 进行封停 5 分钟

1.统计访问IP,redis 中:  1) "RATE_LIMIT:45.140.167.195"

2.将1 分钟超过 300 的 IP加入黑名单: 1) "BLACKLIST:34.174.231.221"

3.避免频繁访问 reids,设置为 10s访问一次,并将黑名单中的 IP先放置在 openresty所在服务器缓存中:

nginx.conf :lua_shared_dict ip_blacklist 10m;

  yum install -y gperftools gperftools-devel perl-ExtUtils-Embed gcc make pcre-devel openssl-devel zlib-devel

git clone https://github.com/vozlt/nginx-module-vts.git
./configure --prefix=/usr/local/openresty-1.25.3.2  --with-http_ssl_module  --with-compat --with-debug --with-file-aio --with-google_perftools_module --with-http_degradation_module --with-http_realip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_perl_module=dynamic --with-http_slice_module --with-http_stub_status_module --with-luajit --with-http_iconv_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-threads --with-http_gunzip_module --with-http_mp4_module --with-http_dav_module --with-http_sub_module --with-http_random_index_module --with-http_flv_module --with-http_auth_request_module --with-http_addition_module --with-http_v2_module --with-pcre  --with-pcre-jit --without-http_redis2_module --with-http_geoip_module=dynamic --with-stream_geoip_module=dynamic --with-http_secure_link_module --with-mail --with-mail_ssl_module --with-http_postgres_module --add-module=/usr/local/src/openresty-1.25.3.2/ngx_cache_purge-2.3  --add-module=/usr/local/src/nginx-module-vtsgmake
gmake install
[root@ip-172-31-33-75 lua]# cat ip_count_noredispass_cache.lua 
-- 初始化模块
local redis = require "resty.redis"
local ngx = ngx
local shared_cache = ngx.shared.ip_blacklist -- nginx.conf中需配置共享内存

-- 配置参数
-- local REDIS_HOST = "rm-cache2.gnzg6z.ng.0001.apne1.cache.amazonaws.com"
local REDIS_HOST = "172.31.32.167"
local REDIS_PORT = 6379
local IP_BLOCK_TIME = 300    -- 封禁时间(秒)
local RATE_LIMIT = 300        -- 单位时间请求限制
local TIME_WINDOW = 60       -- 时间窗口(秒)
local SYNC_INTERVAL = 10     -- 同步间隔(秒)

-- 获取客户端真实IP(支持代理链)
local function get_client_ip()
    local headers = ngx.req.get_headers()
    return headers["X-Real-IP"] or 
           headers["x_forwarded_for"] or
           ngx.var.remote_addr
end

-- Redis连接池管理
local function close_redis(red)
    if not red then return end
    local ok, err = red:set_keepalive(10000, 100)
    if not ok then
        ngx.log(ngx.ERR, "failed to set keepalive: ", err)
        red:close()
    end
end

-- 同步Redis黑名单到共享内存
local function sync_blacklist()
    local now = ngx.now()
    local last_sync = shared_cache:get("last_sync") or 0
    
    if now - last_sync < SYNC_INTERVAL then return end
    
    local red = redis:new()
    red:set_timeout(2000)
    local ok, err = red:connect(REDIS_HOST, REDIS_PORT)
    if not ok then
        ngx.log(ngx.ERR, "failed to connect redis: ", err)
        return
    end

    -- 使用SCAN安全遍历
    local cursor = "0"
    repeat
        local res, err = red:scan(cursor, "MATCH", "BLACKLIST:*")
        if not res then
            ngx.log(ngx.ERR, "scan failed: ", err)
            break
        end
        cursor = res[1]
        for _, key in ipairs(res[2]) do
            local ip = string.match(key, "BLACKLIST:(.+)")
            if ip then
                shared_cache:set(ip, true, IP_BLOCK_TIME)
            end
        end
    until cursor == "0"

    shared_cache:set("last_sync", now)
    close_redis(red)
end

-- 主处理逻辑
local function main()
    local client_ip = get_client_ip()
    
    -- 1. 检查本地黑名单
    local message = "Your visit is too frequent. Please try again in 5 minutes."
    if shared_cache:get(client_ip) then
        ngx.exit(403)
        return
    end
    
    -- 2. 异步同步黑名单
    ngx.timer.at(0, function(premature)
        if premature then return end
        sync_blacklist()
    end)
    
    -- 3. 频率控制
    local red = redis:new()
    red:set_timeout(2000)
    local ok, err = red:connect(REDIS_HOST, REDIS_PORT)
    if not ok then
        ngx.log(ngx.ERR, "failed to connect: ", err)
        return close_redis(red)
    end
    
    -- 原子化操作
    local key = "RATE_LIMIT:"..client_ip
    local current = red:incr(key)
    if current == 1 then
        red:expire(key, TIME_WINDOW)
    elseif current >= RATE_LIMIT then
        red:set("BLACKLIST:"..client_ip, 1)
        shared_cache:set(client_ip, true, IP_BLOCK_TIME)
        close_redis(red)
        ngx.exit(429)  -- Too Many Requests
    end
    
    close_redis(red)
end

-- 执行主逻辑
main()
lua 封停脚本

1.安装openresty-1.21.4.4

tar -xzvf openresty-1.21.4.4.tar.gz
cd openresty-1.21.4.4
mkdir modules

# 到 github中下载ngx_cache_purge-2.3,解压后放到 modules里面
wget http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gz

# 编译安装 openresty
./configure --prefix=/usr/local/openresty-1.21.4.4 --with-http_ssl_module  --with-compat --with-debug --with-file-aio --with-google_perftools_module --with-http_degradation_module --with-http_realip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_perl_module=dynamic --with-http_slice_module --with-http_stub_status_module --with-luajit --with-http_iconv_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-threads --with-http_gunzip_module --with-http_mp4_module --with-http_dav_module --with-http_sub_module --with-http_random_index_module --with-http_flv_module --with-http_auth_request_module --with-http_addition_module --with-http_v2_module --with-pcre  --with-pcre-jit --without-http_redis2_module --with-http_geoip_module=dynamic --with-stream_geoip_module=dynamic --with-http_secure_link_module --with-mail --with-mail_ssl_module --with-http_postgres_module --add-module=/usr/local/src/openresty-1.21.4.4/ngx_cache_purge-2.3
gmake gmake install

2.安装 redis

自己安装并设置密码

3.lua脚本设置

脚本网上一堆,自己测试下

cd /usr/local/openresty-1.21.4.4
mkdir lua

cat ipblock.lua

ip_bind_time = 30  --封禁IP多长时间
ip_time_out = 10    --指定统计ip访问频率时间范围
connect_count = 10 --指定ip访问频率计数最大值
--上面的意思就是10秒内访问超过10次,自动封 IP 30秒,实际测试下来 5 次后就封了,每次访问*2。
 
--连接redis
local redis = require "resty.redis"
local cache = redis.new()
local ok , err = cache.connect(cache,"10.1.1.240","6380")
-- redis密码
local res, err = cache:auth("wg1q2w3e")
cache:set_timeout(60000)
 
--如果连接失败,跳转到脚本结尾
if not ok then
  goto Lastend
end
 
--查询ip是否在封禁段内,若在则返回403错误代码
--因封禁时间会大于ip记录时间,故此处不对ip时间key和计数key做处理
is_bind , err = cache:get("bind_"..ngx.var.remote_addr)
 
if is_bind == '1' then
  ngx.exit(ngx.HTTP_FORBIDDEN)
  -- 或者 ngx.exit(403)
  -- 当然,你也可以返回500错误啥的,搞一个500页面,提示,亲您访问太频繁啥的。
  goto Lastend
end
 
start_time , err = cache:get("time_"..ngx.var.remote_addr)
ip_count , err = cache:get("count_"..ngx.var.remote_addr)
 
--如果ip记录时间大于指定时间间隔或者记录时间或者不存在ip时间key则重置时间key和计数key
--如果ip时间key小于时间间隔,则ip计数+1,且如果ip计数大于ip频率计数,则设置ip的封禁key为1
--同时设置封禁key的过期时间为封禁ip的时间
 
if start_time == ngx.null or os.time() - start_time > ip_time_out then
  res , err = cache:set("time_"..ngx.var.remote_addr , os.time())
  res , err = cache:set("count_"..ngx.var.remote_addr , 1)
else
  ip_count = ip_count + 1
  res , err = cache:incr("count_"..ngx.var.remote_addr)
  if ip_count >= connect_count then
    res , err = cache:set("bind_"..ngx.var.remote_addr,1)
    res , err = cache:expire("bind_"..ngx.var.remote_addr,ip_bind_time) --fix keys
  end
end
--结尾标记
::Lastend::
local ok, err = cache:close()

另外一种方式,自己分析出 IP并加入到 redis中来禁止该 IP访问

-- ip_block.lua 核心逻辑
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)  -- 连接超时设置:ml-citation{ref="1" data="citationList"}

local function get_client_ip()
    return ngx.var.http_x_forwarded_for or ngx.var.remote_addr
end

local ip = get_client_ip()
local blacklist = ngx.shared.ip_blacklist

-- 检查本地缓存
if blacklist:get(ip) then
    return ngx.exit(ngx.HTTP_FORBIDDEN)
end

-- Redis连接与验证
local ok, err = red:connect("10.1.1.240", 6379)
if not ok then
    ngx.log(ngx.ERR, "Redis连接失败: ", err)
    return
end

-- 检查Redis黑名单
local is_banned, err = red:sismember("ip_blacklist", ip)
if is_banned == 1 then
    blacklist:set(ip, true, 60)  -- 缓存1小时:ml-citation{ref="1,4" data="citationList"}
    return ngx.exit(ngx.HTTP_FORBIDDEN)
end

-- 释放连接到连接池
red:set_keepalive(10000, 100) :ml-citation{ref="1" data="citationList"}


##########
#添加封停IP
redis-cli SADD ip_blacklist 1.2.3.4

redis-cli EXPIRE 1.2.3.4 86400  # 24小时后自动解封
ip_deny.lua

4.openreststy启动

nginx.conf添加配置

http {
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
    # 其它参数,有可能用到
    lua_shared_dict ip_blacklist 10m;
    lua_code_cache on;
}
cat localhost.conf 
server {
    listen 80;
    server_name localhost;

    location / {
        root html;
        index index.html index.htm;
        access_by_lua_file "/usr/local/openresty-1.21.4.4/lua/ipblock.lua";
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root html;          
    }
    access_log /data/logs/nginx/localhost_acc.log;
    error_log /data/logs/nginx/localhost_err.log;
}

5.访问并查看 redis数据

http://IP 统计刷新次数,查看日志

查看 redis

 

posted on 2024-09-04 12:15  杨梅冲  阅读(150)  评论(0)    收藏  举报