一、背景
為了有效封禁某些爬蟲(chóng)或惡意用戶對(duì)服務(wù)器的請(qǐng)求,或者只允許某些來(lái)自指定白名單IP用戶訪問(wèn)服務(wù)器,我們需要建立一個(gè)動(dòng)態(tài)的 IP 黑/白名單機(jī)制。對(duì)于被列入黑/白名單的 IP 地址,我們將拒絕/同意為其提供服務(wù)。
二、架構(gòu)選擇
實(shí)現(xiàn) IP 黑/白名單的功能有多種途徑:
- 操作系統(tǒng)層面:通過(guò)配置
iptables
來(lái)拒絕/同意指定 IP 的網(wǎng)絡(luò)請(qǐng)求; - Web Server 層面:利用 Nginx 自身的
deny
指令或者 Lua 插件來(lái)配置 IP 黑/白名單; - 應(yīng)用層面:在請(qǐng)求服務(wù)之前檢查客戶端 IP 是否在黑名單中。
為了便于管理和共享,我們選擇了 Nginx + Lua + Redis/Txt文本文件 的架構(gòu)來(lái)實(shí)現(xiàn) IP 黑/白名單功能。架構(gòu)圖如下:
三、實(shí)現(xiàn)步驟
1. 安裝 Nginx + Lua 模塊
推薦使用 OpenResty,這是一個(gè)集成了各種 Lua 模塊的 Nginx 服務(wù)器發(fā)行版,方便快速部署 Lua 支持。
2. 安裝并啟動(dòng) Redis 服務(wù)器 / 或者利用Txt文本文件來(lái)儲(chǔ)存IP黑/白名單
確保 Redis 已安裝并正常運(yùn)行。本次通過(guò) Docker 快速啟動(dòng) Redis:
docker run -itd --name redis -p 6379:6379 redis
然后在 Redis 中創(chuàng)建一個(gè)集合用于存儲(chǔ)黑/白名單 IP:
redis-cli
SADD ip_blacklist "192.168.56.1"
3. 配置 Nginx
3.1 修改 nginx.conf
在 Nginx 配置文件中添加以下內(nèi)容,分配一塊共享內(nèi)存空間用于緩存 IP 黑/白名單,并指定 Lua 腳本位置:
http {
# 分配 1M 共享內(nèi)存用于存儲(chǔ) IP 黑名單
lua_shared_dict ip_blacklist 1m;
server {
listen 80;
server_name localhost;
location = /ipblacklist {
access_by_lua_file lua/ip_blacklist.lua;
default_type text/html;
content_by_lua '
ngx.say("<p>hello, lua</p>")
';
}
}
}
4. 編寫(xiě) Lua 腳本
將以下 Lua 腳本保存為 ip_blacklist.lua
,該腳本會(huì)定期從 Redis 或讀取 Txt文本文件 獲取最新的黑/白名單數(shù)據(jù),并更新本地緩存。
-- Redis服務(wù)器地址
local redis_host = "your.redis.server.here"
-- Redis服務(wù)器端口
local redis_port = 6379
-- Redis連接超時(shí)時(shí)間(毫秒),不要設(shè)置得太高!
local redis_connect_timeout = 100
-- 要檢查的黑名單集合的鍵名
local redis_key = "ip_blacklist"
-- 緩存查找的有效時(shí)間(秒)
local cache_ttl = 60
-- 結(jié)束配置部分
-- 獲取客戶端IP地址
local ip = ngx.var.remote_addr
-- 獲取共享內(nèi)存中的ip_blacklist
local ip_blacklist = ngx.shared.ip_blacklist
-- 獲取上次更新的時(shí)間戳
local last_update_time = ip_blacklist:get("last_update_time")
-- 只有在cache_ttl秒之后才從Redis更新ip_blacklist:
if last_update_time == nilor last_update_time < (ngx.now() - cache_ttl) then
-- 引入redis模塊
local redis = require"resty.redis";
local red = redis:new();
-- 設(shè)置Redis連接超時(shí)時(shí)間
red:set_timeout(redis_connect_timeout);
-- 嘗試連接到Redis
local ok, err = red:connect(redis_host, redis_port);
ifnot ok then
-- 如果連接失敗,記錄調(diào)試日志
ngx.log(ngx.DEBUG, "Redis connection error while retrieving ip_blacklist: " .. err);
else
-- 從Redis獲取新的ip_blacklist數(shù)據(jù)
local new_ip_blacklist, err = red:smembers(redis_key);
if err then
-- 如果讀取失敗,記錄調(diào)試日志
ngx.log(ngx.DEBUG, "Redis read error while retrieving ip_blacklist: " .. err);
else
-- 替換本地存儲(chǔ)的ip_blacklist為最新的值:
ip_blacklist:flush_all();
for index, banned_ip inipairs(new_ip_blacklist) do
ip_blacklist:set(banned_ip, true);
end
-- 更新時(shí)間戳
ip_blacklist:set("last_update_time", ngx.now());
end
end
end
-- 檢查客戶端IP是否在黑名單中
if ip_blacklist:get(ip) then
-- 如果在黑名單中,記錄調(diào)試日志并拒絕訪問(wèn)
ngx.log(ngx.DEBUG, "Banned IP detected and refused access: " .. ip);
return ngx.exit(ngx.HTTP_FORBIDDEN);
end
5. 測(cè)試與生效
完成以上步驟后,重新加載 Nginx 配置以使更改生效:
nginx -s reload
此時(shí),如果訪問(wèn)者的 IP 在黑/白名單中,將被拒絕/同意訪問(wèn),拒絕時(shí)返回 403 Forbidden
。
四、總結(jié)
通過(guò)上述方法,我們實(shí)現(xiàn)了基于 Nginx + Lua + Redis/Txt文本文件 的 IP 黑/白名單功能,具有以下優(yōu)點(diǎn):
- 輕量高效:配置簡(jiǎn)單,幾乎不增加服務(wù)器性能負(fù)擔(dān);
- 集中管理:多臺(tái)服務(wù)器可以通過(guò)同一個(gè) Redis 實(shí)例 或 TXT文本文件 共享黑/白名單數(shù)據(jù);
- 動(dòng)態(tài)更新:可以手動(dòng)、第三方程序或通過(guò)自動(dòng)化方式更新 Redis/Txt文本文件 中的黑/白名單,無(wú)需重啟服務(wù)即可生效;
- 實(shí)時(shí)校驗(yàn):對(duì)于非白名單中的請(qǐng)求,可以將其轉(zhuǎn)向到一個(gè)身份驗(yàn)證網(wǎng)站,訪問(wèn)者回答正確時(shí),就可以將其IP加入 Redis 實(shí)例 或 TXT文本文件,之后就可以立即訪問(wèn)了。
這種方案非常適合用于需要?jiǎng)討B(tài)控制訪問(wèn)權(quán)限的場(chǎng)景,如反爬蟲(chóng)、安全防護(hù)等。
閱讀原文:原文鏈接
該文章在 2025/8/6 18:50:32 編輯過(guò)