nginx+lua(openresty)實(shí)現(xiàn)黑/白名單權(quán)限控制的示例
openresty在nginx基礎(chǔ)上集成了很多功能,比如可以直接調(diào)用redis,mysql,http接口等服務(wù),比較流行的網(wǎng)關(guān)kong就是通過openresty實(shí)現(xiàn)的。日常開發(fā)和運(yùn)維離不開nginx,實(shí)現(xiàn)稍微復(fù)雜的功能:簡單權(quán)限控制,灰度發(fā)布等就可以通過openresty實(shí)現(xiàn)。
本文通過openresty定時器定期請求http接口獲取更新的黑名單數(shù)據(jù),過濾用戶并做進(jìn)一步的判斷(結(jié)合實(shí)際的業(yè)務(wù)需求)進(jìn)行權(quán)限管控。話不多說,直接上nginx.conf代碼如下。
user root; worker_processes auto; worker_cpu_affinity auto; error_log logs/error.log error; worker_rlimit_nofile 30000; pid logs/nginx.pid; events { use epoll; worker_connections 65535; } http { #include mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; server_tokens off; underscores_in_headers on; keepalive_timeout 10; send_timeout 60; include /usr/local/nginx/conf/online/*.conf; #下面四行和lua相關(guān),重點(diǎn)看一下 lua_shared_dict portalCache 10m;#多進(jìn)程共享內(nèi)存變量 lua_shared_dict commonCache 1m; init_by_lua_file /home/www/example/lua/api_init.lua;#初始化全局變量 init_worker_by_lua_file /home/www/example/lua/api_timer.lua; #啟動定時器定期調(diào)用接口 upstream portalBackend { server xxx weight=3; server xxx weight=1; } upstream labelBackend { server xxx; server xxx; } upstream portalBackendOnQlikTicket { server xxx; server xxx; } upstream portalClient { server xxx; server xxx; } upstream portalAdmin { server xxx; server xxx; } upstream labelFrontend { server xxx; server xxx; } upstream bigScreen { server xxx; server xxx; } upstream mobile { server xxx; server xxx; } upstream portalMobile { server xxx; server xxx; } server { listen 80; server_name localhost hportal-uat.hikvision.com.cn; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://portalClient; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPT IONS'; add_header Access-Control-Allow-Credentials 'true'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Aliv e,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Autho rization,Origin,Accept'; } location /ptclient { proxy_pass http://portalClient/ptclient; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPT IONS'; add_header Access-Control-Allow-Credentials 'true'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Aliv e,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Autho rization,Origin,Accept'; } location /ptadmin { proxy_pass http://portalAdmin/ptadmin; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /api/ { #...............error.log......notice rewrite_log on; #............... rewrite ^/api/(.*)$ /$1 break; proxy_pass http://portalBackend; proxy_http_version 1.1; proxy_redirect off; proxy_set_header Host $host; proxy_set_header Origin ''; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; add_header Access-Control-Allow-Credentials 'true'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep- Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,A uthorization,Origin,Accept'; } location /api/checkVmPermission { default_type text/html; access_by_lua_file /home/www/example/lua/api_access.lua; } location /api/test { content_by_lua_block { local portalCache = ngx.shared.portalCache; ngx.say(portalCache:get("userAccountList")) } } location @router{ rewrite ^.*$ /index.html last; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } #上述代碼段中的接口只需要關(guān)注 /api/checkVmPermission和/api/test 即可。
api_init.lua代碼如下所示:
json = require "cjson.safe" http = require("resty.http") interval = 60 portalCache = ngx.shared.portalCache ipList = {"xxx.xxx.xxx.xxx","xxx.xxx.xxx.xxx"......} portalCache:set("flag", true) portalCache:set("ipList", json.encode(ipList))
api_timer.lua代碼如下所示:
local function newClient() local httpC = http.new() return httpC end local handler = function (premature) local httpc = newClient() local resp,err = httpc:request_uri("http://10.10.10.10:8080", { method = "GET", path = "/xxx/xxx", --請求的校驗(yàn)字段 headers = { ["VM_HEADER"] = "xxxxx" } }) --array為類型table,需要序列化之后才能存入portalCache local array = json.decode(resp.body).data portalCache:set("userAccountList", json.encode(array)) end --只有一個進(jìn)程負(fù)責(zé)定時任務(wù) if 0 == ngx.worker.id() then local ok, err = ngx.timer.every(interval, handler) if not ok then log(ERR, "failed to get userAccountList: ", err) return end end
api_access.lua代碼如下所示:
function account_is_include(value, tab) --string 轉(zhuǎn)table local table = json.decode(tab) for k,v in ipairs(table) do if string.lower(v) == string.lower(value) then return true end end return false end function ip_is_include(value, tab) for k,v in ipairs(tab) do if string.find(value, v) ~= nil then return true end end return false end local function newClient() local httpC = http.new() return httpC end local handler = function (premature) local httpc = newClient() local resp,err = httpc:request_uri("http://xx.xx.xx.xx:xxxx", { method = "GET", path = "/xxx/xxx", headers = { ["VM_HEADER"] = "xxx" } }) local array = json.encode(json.decode(resp.body).data) portalCache:set("userAccountList", array) end --nginx啟動時先調(diào)用一次 if portalCache:get("flag") then portalCache:set("flag", false) handler() end check_userAccount = portalCache:get("userAccountList") check_ip = json.decode(portalCache:get("ipList")) if nil == check_userAccount or nil == check_ip then return ngx.exit(500) end local headers = ngx.req.get_headers() local userAccount = headers["userAccount"] if userAccount == nil then return ngx.exit(401) end if account_is_include(userAccount, check_userAccount) then local clientIP = headers["x-forwarded-for"] if clientIP == nil or string.len(clientIP) == 0 or clientIP == "unknown" then clientIP = headers["Proxy-Client-IP"] end if clientIP == nil or string.len(clientIP) == 0 or clientIP == "unknown" then clientIP = headers["WL-Proxy-Client-IP"] end if clientIP == nil or string.len(clientIP) == 0 or clientIP == "unknown" then clientIP = ngx.var.remote_addr end -- ...............IP......IP,..IP..','.. if clientIP ~= nil and string.len(clientIP) >15 then local pos = string.find(clientIP, ",", 1) clientIP = string.sub(clientIP,1,pos-1) end if clientIP == nil then return ngx.exit(500) end if ip_is_include(clientIP, check_ip) then return ngx.say(userAccount .. " " .. "Welcome to Hportal! (VM-IP:" .. clientIP ..")") else ngx.status = 403 ngx.say("Error! Restricted permissions! Your IP is " .. clientIP .. ". Make sure your IP is within this range: " .. json.encode(check_ip)) end else ngx.say(userAccount .. " " .. "Welcome to Hportal!") end
最后實(shí)現(xiàn)效果如下所示:
常見問題匯總:
1.init_worker_by_lua_file error: /usr/local/openresty/lualib/resty/http.lua:133: API disabled in the context of init_worker_by_lua*
stack traceback:
[C]: in function 'ngx_socket_tcp'
/usr/local/openresty/lualib/resty/http.lua:133: in function 'new'
/home/www/example/lua/api_timer.lua:6: in function 'newClient'
/home/www/example/lua/api_timer.lua:21: in function 'getUserAccountTask'
/home/www/example/lua/api_timer.lua:35: in main chunk
答:在init_worker_by_lua...中不能直接調(diào)報(bào)表http的函數(shù),必須在定時器里面調(diào)用。
2.json.decode(resp.body).data (table類型)無法直接放入公共緩存變量的value中
答:set時需要將table轉(zhuǎn)為string,get時再轉(zhuǎn)為table。
3. 如何訪問http接口
答:推薦使用httpc,openresty本身不支持,需要在/usr/local/openresty/lualib/resty目錄下引入以下三個文件:https://github.com/ledgetech/lua-resty-http/tree/master/lib/resty
4.如何獲取請求真實(shí)ip地址?
local headers=ngx.req.get_headers() local clientIP = headers["x-forwarded-for"] if clientIP == nil or string.len(clientIP) == 0 or clientIP == "unknown" then clientIP = headers["Proxy-Client-IP"] end if clientIP == nil or string.len(clientIP) == 0 or clientIP == "unknown" then clientIP = headers["WL-Proxy-Client-IP"] end if clientIP == nil or string.len(clientIP) == 0 or clientIP == "unknown" then clientIP = ngx.var.remote_addr end -- 對于通過多個代理的情況,第一個IP為客戶端真實(shí)IP,多個IP按照','分割 if clientIP ~= nil and string.len(clientIP) >15 then local pos = string.find(clientIP, ",", 1) clientIP = string.sub(clientIP,1,pos-1) end
到此這篇關(guān)于nginx+lua(openresty)實(shí)現(xiàn)黑/白名單權(quán)限控制的示例的文章就介紹到這了,更多相關(guān)nginx 黑/白名單權(quán)限控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mac M1 Nginx 配置多站點(diǎn)的實(shí)現(xiàn)
這篇文章主要介紹了Mac M1 Nginx 配置多站點(diǎn)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03開發(fā)環(huán)境服務(wù)器vs生產(chǎn)環(huán)境服務(wù)器:開發(fā)與生產(chǎn)須分明詳解
開發(fā)環(huán)境服務(wù)器(如Vite)和生產(chǎn)環(huán)境服務(wù)器(如Nginx和Node.js)在職責(zé)和工作方式上存在顯著差異,開發(fā)環(huán)境服務(wù)器專注于快速開發(fā)和調(diào)試,而生產(chǎn)環(huán)境服務(wù)器則強(qiáng)調(diào)穩(wěn)定性和高并發(fā)處理,Vite適合開發(fā)環(huán)境,而Nginx和Node.js更適合生產(chǎn)環(huán)境2025-01-01Nginx實(shí)現(xiàn)請求的超時自動重試的方法示例
在當(dāng)今數(shù)字化的快節(jié)奏世界中,我們的網(wǎng)絡(luò)應(yīng)用就像是繁忙的交通樞紐,每天都要處理海量的請求,我們需要一種像“備用路線”一樣的機(jī)制,也就是請求的超時自動重試,本文就給大家介紹了Nginx?中怎樣實(shí)現(xiàn)請求的超時自動重試,需要的朋友可以參考下2024-07-07upstream模塊中常用options選項(xiàng)講解
這篇文章主要為大家介紹了upstream模塊中常用options選項(xiàng)講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Nginx配置參數(shù)中文說明詳解(負(fù)載均衡與反向代理)
最近在看高性能Linux服務(wù)器構(gòu)建實(shí)戰(zhàn)的Nginx章節(jié),對其nginx介紹的非常詳細(xì),現(xiàn)把經(jīng)常用到的Nginx配置參數(shù)中文說明摘錄和nginx做負(fù)載均衡的本人真實(shí)演示實(shí)例抄錄下來以便以后查看2020-03-03