禁止のLua + Redisの動的なIPによってopenresty開発シリーズ38--

Luaの+ Redisの動的なIPによってopenresty開発シリーズ38--禁止

)需要の背景
特定の爬虫類やサーバへの悪質なユーザーの要求を禁止するが、我々は、動的IPのブラックリストを構築する必要があります。
IP、サービス拒否内のブラックリストのため。

B)設計
IPブラックリスト機能の実装は、多くの方法がある:
1、オペレーティング・システム・レベル、iptablesの設定、IPネットワークリクエストを指定することを拒否し、
Webサーバレベルで、2、nginxの構成IPブラックは単独でオプションを拒否またはプラグインLUAリスト;
3は、アプリケーションレベルで、サービスを要求する前にブラックリストにクライアントIPかどうかを再度確認してください。

管理および共有を容易にするために、我々は、nginxのことでIPブラックリスト機能のためのアーキテクチャのLuaの+ Redisのを+

に示すように

、構成nginx.conf
すべての要求のRedisのは避けて、キャッシュデータのRedisにローカルキャッシュを設定し、HTTPセクションで

shared_ip_blacklistをlua_shared_dict 8メートル;#変数ローカルキャッシュip_blacklistの定義

場所/ ipblacklist {
    /usr/local/lua/access_by_limit_ip.lua access_by_lua_file;
    "ipblacklist"エコー;
}


#編集は/ usr / ローカル /ルア/ access_by_limit_ip.lua 

ローカル 関数close_redis(レッド)
     IF  ないレッド、その後は  
        返す
    エンド  
    - 取り外し可能な接続(接続プールの実装)   
    ローカル pool_max_idle_time = 10000  - ミリ秒   
    ローカル POOL_SIZE = 100  - 接続プールは、サイズ   
    ローカル OK、ERR = 赤:set_keepalive(pool_max_idle_time、POOL_SIZE)  
     IF  ない OK 、その後  
        ngx.say(" SETキープアライブエラー:" 、ERR)  
     エンド  
エンド

ローカル 関数ERRLOG(...)
    ngx.log(ngx.ERR、" Redisの:" 、...)
 終了

ローカル 関数duglog(...)
    ngx.log(ngx.DEBUG、" Redisのを:" 、...)
 終了

ローカル 関数getIp()
     ローカル MYIP = ngx.req.get_headers()[ " X-実IP " ]
     もし MYIP == ゼロ 次いで
        MYIP = ngx.req.get_headers()" x_forwarded_for " ]
     の端を
    場合 == MYIP nilの その後
        MYIP = ngx.var.remote_addr
     エンド
    リターンMYIP。
終了

ローカルキーを= " リミット:IP:ブラックリスト" 
ローカル IP = getIp();
ローカル shared_ip_blacklist = ngx.shared.shared_ip_blacklist 

- 获得本地缓存的最新刷新时间
ローカル LAST_UPDATE_TIME = shared_ip_blacklist:(取得" LAST_UPDATE_TIMEを" ); 

もし LAST_UPDATE_TIME〜= nilの 後、 
    地元の dif_time = ngx.now() - LAST_UPDATE_TIME 
     場合 dif_time < 60  それから -バッファ1分の有効期限が切れていない
        IF shared_ip_blacklist:GET(IP) その後
            返す ngx.exit(ngx.HTTP_FORBIDDEN)- ダイレクトリターン403 
        エンド
        返す
    エンド
エンド

地元のRedisを= 必要 resty.redis   - 導入Redisのモジュール
地元レッド= Redisの:新新()   - コロンの呼び出しに注意し、オブジェクトを作成します

- ミリ秒単位)タイムアウトを設定   
赤:SET_TIMEOUT(1000年- 接続の確立に   
ローカル IP = 10.11.0.215   
ローカルポート= 6379 
ローカル OK、ERRを=赤:接続(IP、ポート)
 IF  ない OK 、その後  
    close_redis(レッド)
    ERRLOG(" リミットIPはRedisのを接続することはできません" );
 他の
    ローカル ip_blacklist、ERR = 赤:smembers(キー); 
    
    IF ERR その後、
        ERRLOG(" リミットIPのsmembers " ;)
     他の
        - リセット、ローカルキャッシュを更新し
        ;)flush_all(:shared_ip_blacklistを
        
        - 同期のRedisは、ローカルキャッシュにブラックリスト
        のための I、BIP  ipairs(ip_blacklist)を行う
            - Redisのブラックリスト内のローカルキャッシュ
            shared_ip_blacklist:SET(BIP、真の);
         エンド
        - ローカルキャッシュの最後の更新を設定するには 
        shared_ip_blacklist:SET(" LAST_UPDATE_TIME " 、ngx.now()を);
     エンド
エンド  

のIF shared_ip_blacklist:(IP)をGET その後
    返す NGX(ngx.exitを.HTTP_FORBIDDEN)- ダイレクトリターン403 
終了
 

次のようにパスワードが設定されているRedisのコードがある:

CAT番号の/usr/local/lua/access_by_limit_ip.lua [ノード5ルートのLua @]

local function close_reis(red)
    if not red then
        return
    end
    local pool_max_idle_time = 10000
    local pool_size = 100
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
    if not ok then
        ngx.say("set keepalive error :", err)
    end
end

local function errlog(...)
    ngx.log(ngx.ERR, "redis: ", ...)
end

local function duglog(...)
    ngx.log(ngx.DEBUG, "redis: ",...)
end

local function getIp()
    local myip = ngx.req.get_headers()["X-Real-IP"]
    if myip == nil then
        myip = ngx.req.get_headers()["x_forwarded_for"]
    end
    if myip == nil then
        myip = ngx.var.remote_addr
    end
    return myip
end

local key = "limit:ip:blacklist"
local ip = getIp();
local shared_ip_blacklist = ngx.shared.shared_ip_blacklist

local last_update_time = shared_ip_blacklist:get("last_update_time");

if last_update_time ~= nil then
    local dif_time = ngx.now() - last_update_time
    if dif_time < 60 then
        if shared_ip_blacklist:get(ip) then
            return ngx.exit(ngx.HTTP_FORBIDDEN)
        end
        return
    end
end

local redis = require "resty.redis"
local red = redis:new()

red:set_timeout(1000)
local ip = "10.11.0.215"
local port = 6379
local ok, err = red:connect(ip,port)

local count, err = red:get_reused_times()
if 0 == count then ----新建连接,需要认证密码
    ok, err = red:auth("redis123")
    if not ok then
        ngx.say("failed to auth: ", err)
        return
    end
elseif err then  ----从连接池中获取连接,无需再次认证密码
    ngx.say("failed to get reused times: ", err)
    return
end

if not ok then
    close_redis(red)
    errlog("limit ip cannot connect redis");
else
    local ip_blacklist, err = red:smembers(key)

    if err then
        errlog("limit ip smembers")
    else
        shared_ip_blacklist:flush_all();

        for i,bip in ipairs(ip_blacklist) do
            shared_ip_blacklist:set(bip, true);
        end

        shared_ip_blacklist:set("last_update_time", ngx.now());
    end
end

if shared_ip_blacklist:get(ip) then
    return ngx.exit(ngx.HTTP_FORBIDDEN)
end

用户redis客户端设置:
添加黑名单IP:
sadd limit:ip:blacklist 10.11.0.148

获取黑名单IP:
smembers limit:ip:blacklist


10.11.0.215:6379> sadd limit:ip:blacklist 10.11.0.148
10.11.0.215:6379> sadd limit:ip:blacklist 10.11.0.215

10.11.0.215:6379> smembers limit:ip:blacklist
1) "10.11.0.215"
2) "10.11.0.148"
10.11.0.215:6379> smembers limit:ip:blacklist
1) "10.11.0.215"
2) "10.11.0.148"


此方法目前只能实现手动添加黑名单IP进行IP封禁,在某些场景如:半夜如果有人恶意爬取网站服务器可能导致服务器资源耗尽崩溃或者影响业务


下面是改进后的代码,可以实现自动将访问频次过高的IP地址加入黑名单封禁一段时间


nginx.conf配置部分:
location /goodslist {
        set $business "USER";
        access_by_lua_file /usr/local/lua/access_count_limit.lua;
        echo "get goods list success";
    }


lua代码:

[root@node5 lua]# cat /usr/local/luaaccess_count_limit.lua

local function close_redis(red)
    if not red then
        return
    end

    local pool_max_idle_time = 10000
    local pool_size = 100
    local ok, err = red:set_keepalive(pool_max_idle_tme, pool_size)
    if not ok then
        ngx.say("set keepalive err : ", err)
    end
end


local ip_block_time=300 --封禁IP时间(秒)
local ip_time_out=30    --指定ip访问频率时间段(秒)
local ip_max_count=20 --指定ip访问频率计数最大值(秒)
local BUSINESS = ngx.var.business --nginx的location中定义的业务标识符
 
--连接redis
local redis = require "resty.redis"  
local conn = redis:new()  
ok, err = conn:connect("10.11.0.215", 6379)  
conn:set_timeout(2000) --超时时间2秒
 
--如果连接失败,跳转到脚本结尾
if not ok then
    --goto FLAG
   close_redis(conn)
end

local count, err = conn:get_reused_times()
if 0 == count then ----新建连接,需要认证密码
    ok, err = conn:auth("redis123")
    if not ok then
        ngx.say("failed to auth: ", err)
        return
    end
elseif err then  ----从连接池中获取连接,无需再次认证密码
    ngx.say("failed to get reused times: ", err)
    return
end

--查询ip是否被禁止访问,如果存在则返回403错误代码
is_block, err = conn:get(BUSINESS.."-BLOCK-"..ngx.var.remote_addr)  
if is_block == '1' then
    ngx.exit(403)
    close_redis(conn)
end
 
--查询redis中保存的ip的计数器
ip_count, err = conn:get(BUSINESS.."-COUNT-"..ngx.var.remote_addr)
 
if ip_count == ngx.null then --如果不存在,则将该IP存入redis,并将计数器设置为1、该KEY的超时时间为ip_time_out
    res, err = conn:set(BUSINESS.."-COUNT-"..ngx.var.remote_addr, 1)
    res, err = conn:expire(BUSINESS.."-COUNT-"..ngx.var.remote_addr, ip_time_out)
else
    ip_count = ip_count + 1 --存在则将单位时间内的访问次数加1
  
    if ip_count >= ip_max_count then --如果超过单位时间限制的访问次数,则添加限制访问标识,限制时间为ip_block_time
        res, err = conn:set(BUSINESS.."-BLOCK-"..ngx.var.remote_addr, 1)
        res, err = conn:expire(BUSINESS.."-BLOCK-"..ngx.var.remote_addr, ip_block_time)
    else
        res, err = conn:set(BUSINESS.."-COUNT-"..ngx.var.remote_addr,ip_count)
        res, err = conn:expire(BUSINESS.."-COUNT-"..ngx.var.remote_addr, ip_time_out)
    end
end
 
-- 结束标记
local ok, err = conn:close()


# redis的数据
10.11.0.215:6379> get USER-COUNT-10.11.0.148
"16"
10.11.0.215:6379> get USER-BLOCK-10.11.0.148
(nil)


四、总结

以上,便是 Nginx+Lua+Redis 实现的 IP 黑名单功能,具有如下优点:

1、配置简单、轻量,几乎对服务器性能不产生影响;

2、多台服务器可以通过Redis实例共享黑名单;

3、动态配置,可以手工或者通过某种自动化的方式设置 Redis 中的黑名单。

おすすめ

転載: www.cnblogs.com/reblue520/p/11419918.html