近期,因爲某種緣由,讓我這邊再次關注到服務器身上。在咱們的服務器中,自己是使用 nginx 做爲 web 服務器的,雖然 nginx 自己支持限流和 ip 限制設置,但在動態設置黑名單中,nginx 這邊自己是作不了。這時候,就可使用到 OpenResty 了。html
關於 OpenResty,其實網上和自己中文主頁已經有很是詳細的介紹了,我這邊就不一一闡述了,惟一要注意的是,由於 OpenResty 自己已經包含 nginx 了,若是以前服務器上已經在使用 nginx,必須先把當前版本的 nginx 卸載掉,而後再安裝 OpenResty,因此須要先把原來的 nginx 服務停掉而且備份以前的配置文件,而後再進行安裝,我這邊是經過 yum 進行安裝的,具體使用的指令nginx
# 刪除本來的nginx,記得先備份配置
systemctl disable nginx.service
rm -rf /usr/lib/systemd/system/nginx.service
yum erase nginx
# 安裝 yum-utils
yum install yum-utils
# yum 安裝 openresty
yum install openresty
# 配置 nginx profile PATH
PATH=/usr/local/openresty/nginx/sbin:$PATH
export PATH
# 指定配置
nginx -c /usr/local/openresty/nginx/conf/nginx.conf
複製代碼
至此,關於 OpenResty 的簡單準備就完成了,下面咱們來看下如何經過 lua 腳本實現動態黑名單配置。git
首先,由於實踐上是使用 OpenResty 以及下面的 redis 組件,若是自己對 lua 和 redis 不太熟悉的話,須要先基本瞭解下相關的知識,這裏能夠去查閱下 OpenResty 中關於 redis 組件和 nginx 組件的相關說明。github
而後,我這邊是參考起航天空[1]博主這篇文章的作法,而且作了某些小調整。web
在這個過程當中,博主給到我這邊許多意見和見解,而且很耐心地聽取我這邊的一些建議和給到解答,對於本人在運維方面上,也給到一些其餘的解決方法,在此表示十分的感謝。redis
下面就直接發下處理的代碼,首先是配置代碼:shell
set $redis_service "127.0.0.1";
set $redis_port 6380;
set $redis_db 0;
# 1 second 50 query
set $black_count 50;
set $black_rule_unit_time 1;
set $black_ttl 3600;
set $auto_blacklist_key blackkey;
複製代碼
這裏跟例子中的配置沒什麼明顯的差異,分別來講明一下各個配置的含義:apache
這個依據我的喜愛和需求來設定,通常狀況下控制好 black_count 和 black_rule_unit_time 就行。bash
接着是這個具體的 lua 腳本代碼,其中大部分也是按照例子中的來:服務器
local redis_service = ngx.var.redis_service
local redis_port = tonumber(ngx.var.redis_port)
local redis_db = tonumber(ngx.var.redis_db)
local black_count = tonumber(ngx.var.black_count)
local black_rule_unit_time = tonumber(ngx.var.black_rule_unit_time)
local cache_ttl = tonumber(ngx.var.black_ttl)
local remote_ip = ngx.var.remote_addr
-- 計數
function my_count(redis, status_key, count_key)
local key = status_key
local key_connect_count = count_key
local Status = redis:get(key)
local count = redis:get(key_connect_count)
if Status ~= ngx.null then
-- 狀態爲connect 且 count不爲空 且 count <= 拉黑次數
if (Status == "Connect" and count ~= ngx.null and tonumber(count) <= black_count) then
-- 再讀一次
count = redis:incr(key_connect_count)
ngx.log(ngx.ERR, "count:", count)
if count ~= ngx.null then
if tonumber(count) > black_count then
redis:del(key_connect_count)
redis:set(key,"Black")
-- 永久封禁
-- Redis:expire(key,cache_ttl)
else
redis:expire(key_connect_count,black_rule_unit_time)
end
end
else
ngx.log(ngx.ERR,"The visit is blocked by the blacklist because it is too frequent. Please visit later.")
return ngx.exit(ngx.HTTP_FORBIDDEN)
end
else
local count = redis:get(key)
if count == ngx.null then
redis:del(key_connect_count)
end
redis:set(key,"Connect")
redis:set(key_connect_count,1)
redis:expire(key,black_rule_unit_time)
redis:expire(key_connect_count,black_rule_unit_time)
end
end
-- 讀取token
local token
local header = ngx.req.get_headers()["Authorization"]
if header ~= nil then
token = string.match(header, 'token (%x+)')
end
local redis_connect_timeout = 60
local redis = require "resty.redis"
local Redis = redis:new()
local auto_blacklist_key = ngx.var.auto_blacklist_key
Redis:set_timeout(redis_connect_timeout)
local RedisConnectOk,ReidsConnectErr = Redis:connect(redis_service,redis_port)
local res = Redis:auth("password");
if not RedisConnectOk then
ngx.log(ngx.ERR,"ip_blacklist connect Redis Error :" .. ReidsConnectErr)
else
-- 鏈接成功
Redis:select(redis_db)
local key = auto_blacklist_key..":"..remote_ip
local key_connect_count = auto_blacklist_key..":key_connect_count:"..remote_ip
my_count(Redis, key, key_connect_count)
if token ~= nil then
local token_key, token_key_connect_count
token_key = auto_blacklist_key..":"..token
token_key_connect_count = auto_blacklist_key..":key_connect_count:"..token
my_count(Redis, token_key, token_key_connect_count)
end
end
複製代碼
由於在 lua 的實踐上本人也是屬於一個新手的級別,因此在結構性上都有很明顯的問題,這裏先留一個坑。
先解釋下這段代碼,由於我這邊是從 ip 及 token(訪問憑證) 入手來控制,因此先將參考例子中的計數整合在一個 function 當中。function 裏頭本來例子中是使用 set
方法來作加一操做的,因此在大量請求進入的時候,會產生一個同步問題,因此我這邊稍微改造一下,使用 incr
來作一個自增操做,而且在進入方法時就獲取計數值並判斷計數值大小是否超過閾值 black_count
,一次來規避大量請求時產生的問題。
接着是下面獲取 token 中,我是根據應用中使用的憑證作法,從頭部得到 Authorization,而且從中截取來拿到 token,若是 token 爲空,就證實不須要通過 token 的計數處理。
最後是鏈接並調用函數了,這裏沒什麼要說明的地方,主要說明在定義 function 和使用 function 的順序須要注意一下。
而後是實配到 nginx 的 conf 當中了:
server {
listen 80;
server_name blog.mintrumpet.fun;
root /~/public;
# 加載配置文件
include /etc/nginx/conf.d/blacklist_params;
# 指定請求中須要執行的 lua 腳本
access_by_lua_file /etc/nginx/conf.d/ip_blacklist.lua;
location / {
}
error_log /etc/nginx/conf.d/log/error.log;
access_log /etc/nginx/conf.d/log/access.log;
}
複製代碼
以上,配置就完成了,在 console 中重啓下 nginx nginx -s reload
,就能夠實現動態添加黑名單的須要了。至於對於添加到黑名單的 ip 及 token,須要怎麼作下一步的處理,這邊就給服務器下的具體應用來處理,在這裏不闡述。
本人在過程當中是使用我的的服務器裏的博客,以及 apache bench 工具來作測試的。
先測試一個不帶 token(遊客) 的例子,訪問一個靜態文件,
我以10秒50次做爲限制,首先是4個併發訪問40次:
ab -n 40 -c 4 http://blog.mintrumpet.fun/dist/music.js
複製代碼
在執行結果中,能夠看到40個請求都順利完成。
再看下 redis 下的值,
還行,還沒超過限制的大小。
接着4個併發訪問100次:
ab -n 100 -c 4 http://blog.mintrumpet.fun/dist/music.js
複製代碼
從結果能夠看到,裏面有49條請求訪問失敗,顯然都被轉到403了。
再看下 redis 下的值,
很顯然,個人ip被屏蔽了,接着去訪問的時候,提示403錯誤,OK,目的達到了。
接着來測試一個 token 的例子,一樣也是訪問一個靜態文件,也是4個併發訪問100次:
ab -n 100 -c 4 -H "Authorization:token 87BF813C6DDB9C01D4525F47908D4C9F" http://blog.mintrumpet.fun/dist/music.js
複製代碼
結果也是同樣,這個token被屏蔽了,後續的訪問也轉發到403。
至此,整個測試就到此結束了,能夠看到不管是遊客訪問,仍是身份訪問,都能起到一樣的效果。
其實這種作法只能用做通常的狀況,並且在配置及編寫的腳本文件中,還有不少須要改善的地方,在此我也只是有樣學樣,各位讀者若是有更好的作法和方案,能夠在下面的討論中告訴讀者我。同時在跟起航天空的博主交流的時候,他也告訴我其餘作限流和攔截的處理來抵制攻擊和監控,例如 fail2ban、OSSEC等。而 nginx 自身也提供一系列限流措施,有興趣的各位能夠自行學習。
至此,關於 OpenResty 的實踐就到這裏結束了,你們能夠關注下個人博客[2] blog.mintrumpet.fun/ 或者 蘆葦科技 來跟我作關於技術上的交流,那麼今天就到此結束吧。Enjoy Coding!
小喇叭
廣州蘆葦科技Java開發團隊
蘆葦科技-廣州專業互聯網軟件服務公司
抓住每一處細節 ,創造每個美好
關注咱們的公衆號,瞭解更多
想和咱們一塊兒奮鬥嗎?lagou搜索「 蘆葦科技 」或者投放簡歷到 server@talkmoney.cn 加入咱們吧
關注咱們,你的評論和點贊對咱們最大的支持
起航天空,www.qhjack.cn/ ↩︎
小喇叭的小屋,blog.mintrumpet.fun/ ↩︎