nginx+lua(openresty)實現黑/白名單權限控制的示例
openresty在nginx基礎上集成了很多功能,比如可以直接調用redis,mysql,http接口等服務,比較流行的網關kong就是通過openresty實現的。日常開發(fā)和運維離不開nginx,實現稍微復雜的功能:簡單權限控制,灰度發(fā)布等就可以通過openresty實現。
本文通過openresty定時器定期請求http接口獲取更新的黑名單數據,過濾用戶并做進一步的判斷(結合實際的業(yè)務需求)進行權限管控。話不多說,直接上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相關,重點看一下
lua_shared_dict portalCache 10m;#多進程共享內存變量
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; #啟動定時器定期調用接口
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;
}
}
}
#上述代碼段中的接口只需要關注 /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",
--請求的校驗字段
headers = {
["VM_HEADER"] = "xxxxx"
}
})
--array為類型table,需要序列化之后才能存入portalCache
local array = json.decode(resp.body).data
portalCache:set("userAccountList", json.encode(array))
end
--只有一個進程負責定時任務
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 轉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啟動時先調用一次
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
最后實現效果如下所示:


常見問題匯總:
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...中不能直接調報表http的函數,必須在定時器里面調用。
2.json.decode(resp.body).data (table類型)無法直接放入公共緩存變量的value中
答:set時需要將table轉為string,get時再轉為table。
3. 如何訪問http接口
答:推薦使用httpc,openresty本身不支持,需要在/usr/local/openresty/lualib/resty目錄下引入以下三個文件:https://github.com/ledgetech/lua-resty-http/tree/master/lib/resty

4.如何獲取請求真實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為客戶端真實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到此這篇關于nginx+lua(openresty)實現黑/白名單權限控制的示例的文章就介紹到這了,更多相關nginx 黑/白名單權限控制內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
開發(fā)環(huán)境服務器vs生產環(huán)境服務器:開發(fā)與生產須分明詳解
開發(fā)環(huán)境服務器(如Vite)和生產環(huán)境服務器(如Nginx和Node.js)在職責和工作方式上存在顯著差異,開發(fā)環(huán)境服務器專注于快速開發(fā)和調試,而生產環(huán)境服務器則強調穩(wěn)定性和高并發(fā)處理,Vite適合開發(fā)環(huán)境,而Nginx和Node.js更適合生產環(huán)境2025-01-01

