在編譯了lua-nginx-module的nginx上,可以方便地使用shared dict特性,在不reload配置文件的情況下實現配置同步。
由於shared dict使用一塊共享內存,因此所有worker均可讀寫,也就不存在一致性的問題。
屏蔽user-agent並屏蔽日誌
1 2 3 4 5 6 7 8 |
if ($http_user_agent ~* (^badbot/useragent$)) { rewrite (.*) /badbot break; } location = /badbot { access_log off; return 403; } |
不屏蔽user-agent(允許其訪問),但屏蔽日誌
1 2 3 4 5 6 7 |
server { set $is_spider 1; if ($http_user_agent ~* "bot|spider|Bot|Disqus|WebIndex|YunGuanCe") { set $is_spider 0; } access_log /var/log/nginx/access.log combined if=$is_spider; } |
按uri屏蔽日誌(可以和上面的按user-agent用同一個變量來同時過濾uri和user-agent)
1 2 3 4 5 6 7 8 9 10 11 |
map $request $loggable { ~/favicon.ico 0; ~/images/* 0; ~/js/* 0; ~/css/* 0; default 1; } server { access_log /var/log/nginx/access.log combined if=$loggable; } |
按mime type設置緩存時間
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
map $sent_http_content_type $cacheable_types { "text/css" "max-age=14400"; "text/plain" "max-age=86400"; "image/jpeg" "max-age=86400"; "image/png" "max-age=86400"; "image/gif" "max-age=86400"; "image/x-icon" "max-age=86400"; "application/x-7z-compressed" "max-age=864000"; "application/x-javascript" "max-age=86400"; "application/json" "max-age=86400"; "application/x-bittorrent" "max-age=864000"; default ""; } server { location / { root "/var/www/html/"; add_header "Cache-Control" $cacheable_types; } } |
簡單的無狀態cookie challenge(需要lua-nginx-module)
crawlers塊中可以手動填寫要屏蔽的IP
將其中的s改成隨機字符串+時間戳可以變成有狀態版本(需使用redis/memcached/shared memory存儲生成的隨機字符串)
將set-cookie改成通過js生成cookie可以變成javascript challenge,注意要在js里加上瀏覽器上下文判斷,如var cookie=location.protocol?cookie:””; 或者DOM操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
geo $crawlers{ 1.2.3.0/24 1; 4.5.6.7 1; default 0; } server { location ~ \.php { set $_c 0; if ($http_user_agent ~* "bad.guy/123") { set $_c 1; } if ($crawlers){ set $_c 1; } if ($_c){ access_log off; access_by_lua_block { local expires = 90 local s = ngx.time() local cob = tonumber(ngx.var.cookie_cob) if cob == nil then cob=0 end local coa = ngx.md5("saltsalt" .. ngx.var.remote_addr .. "saltsalt" .. cob) if s - cob > expires or ngx.var.cookie_coa ~= coa then coa = ngx.md5("saltsalt" .. ngx.var.remote_addr .. "saltsalt" .. s) ngx.header["Set-Cookie"] = {"coa=" .. coa .."; path=/; domain=.example.com; HttpOnly", "cob=" .. s.."; path=/; domain=.example.com; HttpOnly"} ngx.header["Content-Type"] = "text/html" ngx.say("<meta http-equiv ='refresh' content='0'>"); ngx.exit(200) end } } # fastcgi_pass } } |
植入cookie
需要注意的是使用ngx.time()產生秒級的時間,用來做隨機數種子可能會衝突,因此建議加上另外的隨機變量(如下面的例子用的是客戶端的ip) 可以使用ngx.now()產生毫秒精度時間
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
local modname = "util" local _M = { _VERSION = '0.01' } local mt = { __index = _M } function _M.random_str(l, seed, r) local s = r or 'abcdefghijklmnhopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+-' local ret ='' -- os.time() is precise to second math.randomseed(os.time() * 1000 + ngx.crc32_short(seed or "")) for i=1 ,l do local pos = math.random(1, string.len(s)) ret = ret .. string.sub(s, pos, pos) end return ret end function _M.plant_cookie(k, h) local ck = require "resty.cookie" local cookie, err = ck:new() if not cookie then ngx.log(ngx.ERR, "ERROR PLANTING COOKIE", err) return end local _, err = cookie:get(k) if _ then return end local ok, err = cookie:set({ key = k, value = _M.random_str(32, ngx.var.remote_addr, '0123456789abcdef'), path = "/", domain = h or ngx.var.http_host, httponly = true, expires= 'Thu, 31-Dec-37 23:55:55 GMT', max_age = 2147483647 }) end return _M |
1 2 3 4 5 6 |
location / { access_by_lua ' local _ = require("util") _.plant_cookie("cookie_name",".example.come") '; } |
想在api服務器里實現一個acl的功能,對某些請求(需要登錄,需要檢查appkey,需要限制頻次等)做限制,對某些起始狀態(比如登陸)或者終結狀態(比如報錯)的請求放行。
因為lua里木有switch case,因此通過一個acl_list的table去查找規則,因為需要限制的請求種類比較多,就把rule_check_token當成默認值了,一看是是這麼寫的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
local _M = { ACL_PASS = 0, ACL_DENIED = 1, } function _M.rule_always_pass(self) return _M.ACL_PASS end function _M.rule_check_token(query) if query == nil then return _M.ACL_DENIED end local token = query['token'] or ngx.req.get_headers()['Authorization'] local uid = query['uid'] -- ... end _M.acl_list = setmetatable({ ['error'] = _M.rule_always_pass, ['user/login'] = _M.rule_always_pass, }, { __index = _M.rule_check_token }) return _M |
然而卻會在local token= xxxx那一行報stack overflow,想了半天也發現哪裡有無限遞歸,因為query傳進來的是http請求的query string解析出的鍵值對錶。
把query打印出來一看,發現竟然是這個模塊本身……
仔細看了文檔才知道,原來__index後面的值是一個function時,lua會調用這個function去獲得不存在的鍵,並且第一個參數是模塊本身(即_M,一個table)。在這個例子里:
所以就死循環了
所以要好好看文檔
解決方法是可以套一個function
1 |
__index = function() return _M.rule_check_token end |
貼一個打印table的工具,方便調試:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function print_r(tbl, pref) for k,v in pairs(tbl) do if type(v) == 'table' then print((pref or "|-"), k, "\t", "<table>") _print_r(v, " "..(pref or "|-")) else if type(v) == 'function' then v = " <function>" end print((pref or "|-"), k, "\t", v) end end end |
可以打印出如下形式:
1 2 3 4 5 6 7 8 |
|-_VERSION 0.01 |-rule_always_pass <function> |-ACL_DENIED 1 |-acl_list <table> |-error <function> |-user/login <function> |-ACL_PASS 0 |-rule_check_token <function> |
使用openresty請自行改寫成local function和ngx.say的形式