如何控制外部客户端访问API接口的频率,利用lua脚本实现接口限流
mkdir /usr/local/openresty/nginx/html/lua/waf waf/ ├── access.lua #统一入口脚本 ├── config.lua #配置开关 ├── init.lua #初始化函数 └── lib.lua #依赖函数 |
脚本详细
access.lua(统一入口脚本)
require "init" local function waf_main() if cc_attack_check() then else return end end -- main waf_main() |
config.lua(开关配置)
-- on 开启 off 关闭 -- Define waf switch config_waf_enable = "on" -- Define cc switch config_cc_check = "on" -- Define cc rate(CCcount/CCseconds) config_cc_rate = "3/30" |
init.lua(初始化)
require 'lib' require 'config' --[[ 使用了ngx.var.uri,ngx.shared.limit ]] function cc_attack_check() if config_cc_check == 'on' then -- 根据uri + ip生成token来检验 local ATTACK_URI = ngx.var.uri local CC_TOKEN = get_client_ip()..ATTACK_URI local limit = ngx.shared.limit local CCcount=tonumber(string.match(config_cc_rate,'(.*)/')) local CCseconds=tonumber(string.match(config_cc_rate,'/(.*)')) local req,_ = limit:get(CC_TOKEN) -- 超过指定的次数,返回403,否则次数加1 if req then if req >= CCcount then log_record('CC_Acttack',ngx.var.request_uri,"-","-") if config_waf_enable == 'on' then ngx.exit(403) end else limit:incr(CC_TOKEN, 1) end else limit:set(CC_TOKEN, 1, CCseconds) end -- ngx.shared.limit存储token end return end |
lib.lua(依赖工具函数)
function get_client_ip() local headers = ngx.req.get_headers() local CLIENT_IP = headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr if CLIENT_IP == nil then CLIENT_IP = "unknown" end return CLIENT_IP end function get_user_agent() local USER_AGENT = ngx.var.http_user_agent if USER_AGENT == nil then USER_AGENT = 'unknown' end return USER_AGENT end function log_record(method, url, data, ruletag) local cjson = require("cjson") local io = require 'io' local LOG_PATH = "/data/logs/" local CLIENT_IP = get_client_ip() local USER_AGENT = get_user_agent() local SERVER_NAME = ngx.var.server_name local LOCAL_TIME = ngx.localtime() local log_json_obj = { client_ip = CLIENT_IP, local_time = LOCAL_TIME, server_name = SERVER_NAME, user_agent = USER_AGENT, attack_method = method, req_url = url, req_data = data, rule_tag = ruletag, } local LOG_LINE = cjson.encode(log_json_obj) local LOG_NAME = LOG_PATH..'/'..ngx.today().."_waf.log" local file, err = io.open(LOG_NAME,"aw+") if file == nil then return else file:write(LOG_LINE.." ") file:flush() file:close() end end |
#user www; worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; lua_shared_dict limit 50m; lua_package_path "/usr/local/openresty/nginx/html/lua/waf/?.lua;;"; init_by_lua_file "/usr/local/openresty/nginx/html/lua/waf/init.lua"; access_by_lua_file "/usr/local/openresty/nginx/html/lua/waf/access.lua"; server { listen 80; location / { default_type 'text/plain'; content_by_lua_file html/lua/hello.lua; } location /log { default_type 'text/plain'; content_by_lua_file html/lua/get_log.lua; } } } |
/usr/local/openresty/bin/openresty -s reload for i in `seq 1 10` ; do curl -I 127.0.0.1/log 2>/dev/null | awk '/^HTTP/{print $2}' ; done 200 200 200 403 403 403 403 403 403 403 |
| 留言与评论(共有 0 条评论) “” |