openresty开发系列38--通过Lua+Redis 实现动态封禁IP
一)需求背景
為了封禁某些爬蟲或者惡意用戶對(duì)服務(wù)器的請(qǐng)求,我們需要建立一個(gè)動(dòng)態(tài)的 IP 黑名單。
對(duì)于黑名單之內(nèi)的 IP ,拒絕提供服務(wù)。
二)設(shè)計(jì)方案
實(shí)現(xiàn) IP 黑名單的功能有很多途徑:
1、在操作系統(tǒng)層面,配置 iptables,拒絕指定 IP 的網(wǎng)絡(luò)請(qǐng)求;
2、在 Web Server 層面,通過 Nginx 自身的 deny 選項(xiàng) 或者 lua 插件 配置 IP 黑名單;
3、在應(yīng)用層面,在請(qǐng)求服務(wù)之前檢查一遍客戶端 IP 是否在黑名單。
為了方便管理和共享,我們通過 Nginx+Lua+Redis 的架構(gòu)實(shí)現(xiàn) IP 黑名單的功能
如圖
配置nginx.conf
在http部分,配置本地緩存,來緩存redis中的數(shù)據(jù),避免每次都請(qǐng)求redis
lua_shared_dict shared_ip_blacklist 8m; #定義ip_blacklist 本地緩存變量
location /ipblacklist {
?? ?access_by_lua_file /usr/local/lua/access_by_limit_ip.lua;
?? ?echo "ipblacklist";
}
# 編輯 /usr/local/lua/access_by_limit_ip.lualocal function close_redis(red)if not red then returnend --釋放連接(連接池實(shí)現(xiàn)) 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 endlocal function errlog(...)ngx.log(ngx.ERR, "redis: ", ...) endlocal function duglog(...)ngx.log(ngx.DEBUG, "redis: ", ...) endlocal function getIp()local myIP = ngx.req.get_headers()["X-Real-IP"]if myIP == nil thenmyIP = ngx.req.get_headers()["x_forwarded_for"]endif myIP == nil thenmyIP = ngx.var.remote_addrendreturn myIP; endlocal key = "limit:ip:blacklist" local ip = getIp(); local shared_ip_blacklist = ngx.shared.shared_ip_blacklist--獲得本地緩存的最新刷新時(shí)間 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 --緩存1分鐘,沒有過期if shared_ip_blacklist:get(ip) thenreturn ngx.exit(ngx.HTTP_FORBIDDEN) --直接返回403endreturnend endlocal redis = require "resty.redis" --引入redis模塊 local red = redis:new() --創(chuàng)建一個(gè)對(duì)象,注意是用冒號(hào)調(diào)用的--設(shè)置超時(shí)(毫秒) red:set_timeout(1000) --建立連接 local ip = "10.11.0.215" local port = 6379 local ok, err = red:connect(ip, port) if not ok then close_redis(red)errlog("limit ip cannot connect redis"); elselocal ip_blacklist, err = red:smembers(key);if err thenerrlog("limit ip smembers");else--刷新本地緩存,重新設(shè)置 shared_ip_blacklist:flush_all();--同步redis黑名單 到 本地緩存for i,bip in ipairs(ip_blacklist) do--本地緩存redis中的黑名單shared_ip_blacklist:set(bip,true);end--設(shè)置本地緩存的最新更新時(shí)間shared_ip_blacklist:set("last_update_time",ngx.now());end end if shared_ip_blacklist:get(ip) thenreturn ngx.exit(ngx.HTTP_FORBIDDEN) --直接返回403 end
當(dāng)redis設(shè)置了密碼時(shí)代碼如下:
[root@node5 lua]# cat /usr/local/lua/access_by_limit_ip.lua
用戶redis客戶端設(shè)置:
添加黑名單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"
此方法目前只能實(shí)現(xiàn)手動(dòng)添加黑名單IP進(jìn)行IP封禁,在某些場(chǎng)景如:半夜如果有人惡意爬取網(wǎng)站服務(wù)器可能導(dǎo)致服務(wù)器資源耗盡崩潰或者影響業(yè)務(wù)
下面是改進(jìn)后的代碼,可以實(shí)現(xiàn)自動(dòng)將訪問頻次過高的IP地址加入黑名單封禁一段時(shí)間
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
# redis的數(shù)據(jù)
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)
四、總結(jié)
以上,便是 Nginx+Lua+Redis 實(shí)現(xiàn)的 IP 黑名單功能,具有如下優(yōu)點(diǎn):
1、配置簡(jiǎn)單、輕量,幾乎對(duì)服務(wù)器性能不產(chǎn)生影響;
2、多臺(tái)服務(wù)器可以通過Redis實(shí)例共享黑名單;
3、動(dòng)態(tài)配置,可以手工或者通過某種自動(dòng)化的方式設(shè)置 Redis 中的黑名單。
轉(zhuǎn)載于:https://www.cnblogs.com/reblue520/p/11419918.html
總結(jié)
以上是生活随笔為你收集整理的openresty开发系列38--通过Lua+Redis 实现动态封禁IP的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: openresty开发系列37--ngi
- 下一篇: openresty开发系列39--ngi