因發現平臺日誌中不定時會有同一IP傳送大量的正常請求的情況,因程式沒做請求頻率的限制,就打算使用nginx+lua(OpenResty)+redis來做控制,發現請求頻率高的IP,直接封掉,禁止訪問。
一、部署OpenResty
1、安裝工具和依賴
yum -y install wget vim gcc pcre-devel openssl-devel
2、從 https://github.com/openresty/openresty,下載OpenResty,這裡使用openresty-1.25.3.1版本。
wget https://github.com/openresty/openresty/releases/download/v1.25.3.1/openresty-1.25.3.1.tar.gz
3、解壓、配置、編譯、安裝
tar zxf openresty-1.25.3.1.tar.gz cd openresty-1.25.3.1 ./configure --prefix=/data/openresty gmake && gmake install
二、部署redis
用redis來儲存被封的IP,當然也可以考慮使用其他的方式,這裡使用redis,假設redis密碼為123456,傳送陣:部署redis
三、編寫lua指令碼,啟動redis
1、在nginx配置檔案中location模組里加入lua程式碼
vim /data/openresty/nginx/conf/nginx.conf
http { .... #建立一個共享記憶體區域來儲存IP訪問頻率 lua_shared_dict limit_req_store 10m; server { listen 80; .... location / { root html; index index.html index.htm; #新增lua指令碼,access_by_lua_block指令 access_by_lua_block { ngx.header.content_type = "text/plain; charset=utf-8" local request_limit = 50 -- 定義每個IP的請求頻率限制變數 local ttl = 1 -- 定義快取過期時間變數,單位秒 local redis_passwd = "123456" -- 定義redis密碼 local redis_host = "192.168.1.11" -- 定義redis伺服器地址 local redis_port = 6379 -- 定義redis埠資訊 local redis_set_key = "limit_ip" -- 定義redis集合的key資訊 local redis = require "resty.redis" local red = redis:new() red:set_timeout(1000) local ok, err = red:connect(redis_host, redis_port) if not ok then ngx.log(ngx.ERR,"連線redis出錯: ", err) return end ok , err = red:auth(redis_passwd) if not ok then ngx.log(ngx.ERR,"redis授權失敗:", err) return end local cardinality ,err = red:scard(redis_set_key) if cardinality == 0 then local my_limit = ngx.shared.my_limit_req_store local client_ip = ngx.var.remote_addr local request_count, _ = my_limit:get(client_ip) if not request_count then request_count = 1 else request_count = request_count + 1 end my_limit:set(client_ip,request_count,ttl) if request_count > request_limit then red:sadd(redis_set_key,client_ip) ngx.status = ngx.HTTP_TOO_MANY_REQUESTS ngx.log(ngx.ERR,"該IP請求頻率過高,已被禁止訪問!",client_ip) ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS) end else local client_ip = ngx.var.remote_addr local is_member, err = red:sismember(redis_set_key, client_ip) if err then ngx.log(ngx.ERR,"檢查IP是否存在於redis中出錯: ", err) return end if is_member == 0 then local my_limit = ngx.shared.my_limit_req_store local client_ip = ngx.var.remote_addr local request_count, _ = my_limit:get(client_ip) if not request_count then request_count = 1 else request_count = request_count + 1 end my_limit:set(client_ip,request_count,ttl) if request_count > request_limit then red:sadd(redis_set_key,client_ip) ngx.status = ngx.HTTP_TOO_MANY_REQUESTS ngx.log(ngx.ERR,"該IP請求頻率過高,已被禁止訪問!",client_ip) ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS) end else ngx.status = ngx.HTTP_FORBIDDEN ngx.log(ngx.ERR,"該IP已被禁止訪問!",client_ip) ngx.exit(ngx.HTTP_FORBIDDEN) end end red:close() } } header_filter_by_lua_block { ngx.header.content_type = 'text/html; charset=utf-8' } } }
指令碼說明:
a、使用了redis的集合來儲存封禁IP的資訊
b、access_by_lua_block 指令程式碼塊可以放在http、server、location下來控制程式碼影響範圍。
c、request_limit引數的值和ttl的值來控制訪問頻率限制,以上程式碼是沒秒鐘50次請求。
d、根據測試,使用一個靜態的html頁面(沒有css、js等程式碼),請求1次,會執行2次access_by_lua_block指令,未找出具體原因,因此request_limit的值如果為20,ttl為1,那麼在1秒內只能請求10次靜態頁面。
e、該指令碼對IP地址進行永久封禁(除非redis的資料丟失),如果想要實現限制時長功能,可以用其他方式儲存,該方式解封就只能操作redis刪除資料。
2、啟動nginx
/data/openresty/nginx/sbin/nginx