探索使用Nginx +Lua 構建 API 閘道器

PetterLiu發表於2024-06-15

Nginx 是一種網路伺服器,也可用作反向代理、負載平衡器、郵件代理和 HTTP 快取。Nginx 可用於建立 API 閘道器,以事件驅動的方式處理請求,在請求進入伺服器時以快速、低資源佔用的方式處理查詢。此外,它還能降低複雜性,並透過縮短 API 呼叫的平均響應時間最大限度地提高效能. 我們大多數人對 Kong 已經很熟悉了,但我想探索使用 OpenResty 構建 API Gateway 的可能性。


背景

NginxOpenResty之間的聯絡主要體現在以下幾個方面:

  1. 基礎與擴充套件
    • Nginx是一個高效能的HTTP和反向代理web伺服器,同時也是IMAP/POP3/SMTP伺服器。它以穩定性、豐富的功能集、簡單的配置檔案和低系統資源消耗而聞名。
    • OpenResty是基於Nginx的Web平臺,它透過Lua指令碼語言擴充套件了Nginx的功能,提供了一系列高階特性,包括但不限於Lua指令碼支援、Web應用開發框架、流媒體處理等。
  2. 應用場景
    • Nginx適用於大多數Web伺服器和服務能力的需求,特別是當對併發能力有較高要求時。它在中國有廣泛的應用,如百度、京東、新浪、網易、騰訊、淘寶等網站都使用Nginx。
    • OpenResty則更適合需要快速開發和高效能處理的場景,尤其是當業務邏輯較為複雜時。它提供了更強大的Web應用開發能力,可以透過Lua指令碼對Nginx核心和Nginx C模組進行程式設計。
  3. 效能考量
    • Nginx因其輕量級和併發能力強而著稱,能夠在連線高併發的情況下提供穩定的效能。
    • OpenResty雖然使用了LuaJIT作為直譯器,在處理複雜邏輯時的效能可能不如純Nginx,但在某些情況下,尤其是在業務邏輯複雜的環境中,OpenResty可以提供更高的開發效率和靈活性。
  4. 功能性與靈活性
    • Nginx提供了基本的Web伺服器和反向代理服務,以及負載均衡、動靜分離等功能。
    • OpenResty在Nginx的基礎上,透過Lua指令碼擴充套件了更多功能,包括Web應用開發框架、流媒體處理等,提供了更高的靈活性和定製性。
  5. 結構與維護
    • Nginx的程式碼完全用C語言編寫,並已經移植到多個體繫結構和作業系統上。
    • OpenResty打包了標準的Nginx核心、眾多第三方模組以及它們的大多數依賴項。由於OpenResty的維護者也是其中打包的Nginx模組的作者,因此OpenResty可以確保所包含的所有元件可靠地協同工作。

API閘道器(API Gateway)是一個核心的服務架構元件,用於管理、路由和保護對後端服務的訪問。它充當了系統內外的介面,負責接收來自客戶端的請求,並將其路由到相應的後端服務,然後將服務的響應返回給客戶端。API閘道器在現代軟體架構中扮演著至關重要的角色,特別是在微服務架構中。

API閘道器的主要功能

  1. 安全性:負責保護後端服務免受惡意攻擊和未經授權的訪問。可以實施認證、授權、資料加密等安全策略,以確保只有授權的使用者或應用程式可以訪問服務。
  2. 路由和轉發:負責將請求路由到正確的後端服務。可以根據請求的路徑、方法、頭部等條件進行路由,以確保請求被正確地轉發到相應的服務。
  3. 協議轉換和資料轉換:通常需要處理不同的通訊協議和資料格式。可以將來自客戶端的請求轉換成後端服務所需的格式,以實現不同系統之間的互操作性。
  4. 負載均衡和快取:可以分發請求到多個後端服務例項,並且可以對請求進行快取,以減輕後端服務的負載,提高系統的效能和可擴充套件性。
  5. 監控和分析:能夠記錄和監控所有傳入和傳出的請求,提供關於服務效能和使用情況的指標。這些監控資料對於系統的效能最佳化和故障排查非常重要。

Kong與API閘道器的關係

Kong是一個可擴充套件、開源的雲原生API閘道器,它是API閘道器的一種實現。Kong可以在分散式環境中管理、監控和安全地釋出API。Kong提供了流量控制、認證和授權等功能,與API閘道器的主要功能相契合。

Kong是基於OpenResty(Nginx + Lua模組)編寫的高可用、易擴充套件的API Gateway專案。它基於NGINX和Apache Cassandra或PostgreSQL構建,提供了易於使用的RESTful API來操作和配置API管理系統。Kong可以水平擴充套件多個Kong伺服器,透過前置的負載均衡配置把請求均勻地分發到各個Server,以應對大批次的網路請求。


Keycloak是一個開源的身份和訪問管理解決方案,它為現代應用程式和服務提供了強大的身份驗證和授權功能。以下是關於Keycloak的詳細介紹:

  1. 定義與核心功能
    • Keycloak提供了單點登入(SSO)功能,允許使用者使用單一的憑證訪問多個相關但獨立的系統或應用。
    • 它支援多種標準協議,包括OpenID Connect和OAuth 2.0,這使得Keycloak能夠與各種服務進行整合,以提供身份驗證和授權功能。
  2. 主要特點
    • 單點登入/登出:使用者透過Keycloak驗證身份後,可以訪問多個應用程式而無需再次登入;同樣,登出時只需登出一次即可從所有使用Keycloak的應用程式中登出。
    • 使用者管理:Keycloak提供了使用者管理、登入、註冊、密碼策略、安全問題、二步驗證、密碼重置等功能。這些功能可配置且自定義,滿足不同應用的需求。
    • 角色與許可權管理:Keycloak支援使用者角色和許可權管理,以及使用者組功能,幫助管理員更精細地控制訪問許可權。
    • 身份提供商支援:Keycloak支援多種身份提供商,包括社交媒體平臺和其他SSO解決方案,為使用者提供靈活的登入選項。
    • 客戶端介面卡:Keycloak提供了豐富的客戶端介面卡,可以輕鬆整合到各種型別的應用程式中,包括Web應用、移動應用和後端服務等。
  3. 使用場景
    • 企業內部系統:Keycloak可用於企業內部系統,提供統一的身份驗證和訪問控制解決方案。
    • 雲平臺:在雲平臺上,Keycloak可以作為統一的身份和訪問管理中心,為不同的應用和服務提供認證和授權機制。
    • 微服務架構:在微服務架構中,Keycloak可以作為中心化的身份驗證和授權服務,保障微服務之間的安全通訊。
    • 社交登入整合:Keycloak支援與各種社交登入提供商(如Google、Facebook、GitHub等)整合,簡化使用者註冊和登入流程。
  4. 技術細節
    • Keycloak由Red Hat基金會開發和維護,已獲得Apache License 2.0許可證。
    • Keycloak支援LDAP、Active Directory等使用者資訊儲存方式,並提供了使用者聯盟功能,允許使用者從外部身份提供商匯入使用者資訊。
    • Keycloak的管理控制檯允許管理員管理使用者賬戶、角色、許可權等,並配置與應用程式相關的設定。

基礎篇


我們首先需要安裝 OpenResty。OpenResty 可以讓我們使用 Lua 編寫 Nginx 指令碼。安裝完成後,在瀏覽器中輸入 http://localhost。如果一切順利,你將看到下面的截圖:

image

這樣,你就成功安裝了 OpenResty 和 Nginx。現在,我們需要編輯 nginx.conf 檔案。你可以在以下位置找到 nginx.conf 檔案:

Ubuntu : /usr/local/openresty/nginx/conf
Mac /usr/local/Cellar/openresty/nginx/conf (使用自制軟體)
注:也可以在新檔案中新增配置,並將其包含在nginx.conf中。

image

對於基本的 API 閘道器來說,它應該執行兩個重要操作:

  1. 路由
  2. 驗證


路由選擇
要將端點路由到各自的伺服器,您只需要

location /api/test {
proxy_pass http://localhost:8080/api/test;
}

身份驗證
為了進行身份驗證,我將使用 JWT。為此,我們需要使用 OPM(OpenResty 包管理器)安裝
lua-resty-jwt 庫。

opm get SkyLothar/lua-resty-jwt

然後,在 /usr/local/openresty/lualib/resty (Ubuntu) 中建立 jwt-auth.lua,並複製以下程式碼:

local jwt = require “resty.jwt”
local validators = require “resty.jwt-validators”
if ngx.var.request_method ~= “OPTIONS” and not string.match(ngx.var.uri, “login”) then
local jwtToken = ngx.var.http_Authorization
if jwtToken == nil then
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.header.content_type = “application/json; charset=utf-8”
ngx.say(“{\”error\”: \”Forbidden\”}”)
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
local claim_spec = {
exp = validators.is_not_expired()// To check expiry
}
local jwt_obj = jwt:verify(‘secret’, jwtToken, claim_spec)
if not jwt_obj[“verified”] then
ngx.status = ngx.HTTP_UNAUTHORIZED
ngx.header.content_type = “application/json; charset=utf-8”
ngx.say(“{\”error\”: \”INVALID_JWT\”}”)
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
end


上述 Lua 程式碼的作用如下:

  1. 如果是 OPTIONS 呼叫或登入 API,它會將請求轉發到下一行執行。
  2. 否則,它會獲取授權頭中的 JWT 標記。如果令牌不存在,程式碼會返回 "FORBIDDEN"(禁止)。
  3. 然後會驗證 JWT 令牌的真實性和有效期。
  4. 一旦令牌透過驗證,它就會將請求轉發給相應的服務。否則,程式碼將返回 "INVALID_JWT"。


在nginx.conf中加入上述檔案:

location /api/test {
access_by_lua_file /usr/local/openresty/lualib/resty/jwt-auth.lua;
 proxy_pass http://localhost:8080/api/test;
}

就是這樣。您已在 Nginx 上新增了 JWT 身份驗證。

測試
要檢查我們所做的更改,請使用以下命令重啟 OpenResty

sudo service openresty restart

重新啟動後,使用 Postman 或任何其他 REST 客戶端訪問 API 並驗證響應。

提示:您可以使用以下網站生成 JWT 標記: http://jwtbuilder.jamiekurtz.com/

待辦事項
我們可以透過以下方法改進我們的認證指令碼:

  1. 新增不需要 JWT 標記的新 API 端點(如下載 API)的機制。
  2. 目前,指令碼中硬編碼了 JWT 秘密令牌。也許我們可以將其設定為環境變數,使其動態化。

使用Bearer承載器授權的反向代理(使用 Keycloak 身份伺服器)

image

openresty-keycloak-gateway 是一個完整的反向代理示例,支援 JWT 身份驗證。

使用的技術

Keycloak 提供身份驗證、授權、使用者管理等功能
OpenResty (使用 lua-resty-openidc 模組)、網路平臺(如 nginx)
請注意,反向代理需要驗證 JWT 令牌才能轉發請求。在這種情況下,我們需要提供一個 Authorization: Bearer bearer_token_here 頭資訊。

此外,在每個請求中,閘道器都會用一個 X-Real-Name 頭資訊將授權替換為請求。

前提條件
正常執行的 Keycloak 身份伺服器。請參見
此處
一個 Keycloak 領域。參見
此處
訪問型別為公開的 Keycloak 客戶端。
一個簡單的 http echo 伺服器(可選)


Dockerfile

FROM openresty/openresty:alpine-fat
RUN mkdir /var/log/nginx
RUN apk add --no-cache openssl-dev
RUN apk add --no-cache git
RUN apk add --no-cache gcc
RUN luarocks install lua-resty-openidc
ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"]

執行

docker build -t authproxy .
docker run --name authproxy -d -it -p 8000:8000 -v $PWD/nginx.conf:/nginx.conf authproxy -c /nginx.conf

nginx.conf

events {
worker_connections 1024;
}

http {

lua_package_path '~/lua/?.lua;;';

resolver 8.8.8.8;

lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
lua_ssl_verify_depth 5;

# cache for JWT verification results
lua_shared_dict introspection 10m;

server {

listen 8000 default_server;
listen [::]:8000 default_server;

# disbled caching so the browser won't cache the site.
expires 0;
add_header Cache-Control private;

access_by_lua_block {

-- perform base64 encoding
local function toBase64(input)
local output = ngx.encode_base64(input, true)
return output:gsub('%+', '-'):gsub('/', '_')
end

-- serialize to json
local function toJson(input)
return require("cjson").encode(input)
end

-- construct token (X-Real-Name header)
local function toToken(res)

local function roleOf(role)
return "ROLE_" .. role:upper()
end

local function map(func, array)
local new_array = {}
for i,v in ipairs(array) do
new_array[i] = func(v)
end
return new_array
end

local token = {
uuid = res.sub,
username = res.preferred_username,
email = res.email,
first_name = res.given_name,
last_name = res.family_name,
roles = map(roleOf, res.realm_access.roles)
}

return toBase64(toJson(token))
end


local keycloak_base_url = "https://sso.keycloak.test"
local client_id = "test"
local realm = "test"


local opts = {
client_id = client_id,
discovery = keycloak_base_url .. "/auth/realms/" .. realm .. "/.well-known/openid-configuration",

-- IN PRODUCTION SET TO YES
ssl_verify = "no",
}

-- call bearer_jwt_verify to verify JWT signature
local res, err = require("resty.openidc").bearer_jwt_verify(opts)

if err then
ngx.status = 401
ngx.say(err)
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end

-- set X-Real-Name headers with user info
ngx.req.set_header("X-Real-Name", toToken(res))
}

# proxy locations
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# reemove Authorization header
proxy_set_header Authorization "";

# http-echo-server (npm i -g http-echo-server)
proxy_pass
http://host.docker.internal:3000/;
}
}
}

keycloak
更改 client_id
更改 keycloak_base_url 以定位 Keycloak 伺服器。
代理密碼
在位置部分,proxy_pass 用於將請求轉發到另一個服務。在本例中,我們將請求轉發到一個簡單的 echo http 伺服器。

測試 http 伺服器,用於除錯
注意:http-echo-server 會在每個請求中新增 2 秒超時。

npm i -g http-echo-server


測試

Original request

GET /api/v1/test HTTP/1.0
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Bearer bearer_token_here
Accept-Language: en-us

Forwarded request

GET /api/v1/test HTTP/1.0
Host: localhost
X-Real-IP: 172.17.0.1
X-Forwarded-For: 172.17.0.1
X-Forwarded-Proto: http
Connection: close
Accept: */*
User-Agent: Rested/2009 CFNetwork/1128.0.1 Darwin/19.6.0 (x86_64)
Accept-Language: en-us
Accept-Encoding: gzip, deflate
X-Real-Name: ewoJImZpcnN0X25hbWUiOiAidXNlciIsCgkidXVpZCI6ICIwYmM5MTMyNS0xNjk3LTRiNmQtYjRiZi01MGYxZmNmZGMzZWIiLAoJInJvbGVzIjogWyJST0xFX1RFU1RfMSIsICJST0xFX1RFU1RfMiJdLAoJImxhc3RfbmFtZSI6ICJ1c2VyIiwKCSJ1c2VybmFtZSI6ICJ1c2VyIiwKCSJlbWFpbCI6ICJ1c2VyQHRlc3QudGVzdCIKfQ==

X-Real-Name header

是一個 base64 編碼的 json 物件,其中包含使用者資訊。

{
“first_name”: “user”,
“uuid”: “0bc91325–1697–4b6d-b4bf-50f1fcfdc3eb”,
“roles”: [“ROLE_TEST_1”, “ROLE_TEST_2”],
“last_name”: “user”,
“username”: “user”,
“email”: “user@test.test”
}



今天先到這兒,希望對雲原生,技術領導力, 企業管理,系統架構設計與評估,團隊管理, 專案管理, 產品管理,資訊保安,團隊建設 有參考作用 , 您可能感興趣的文章:
構建創業公司突擊小團隊
國際化環境下系統架構演化
微服務架構設計
影片直播平臺的系統架構演化
微服務與Docker介紹
Docker與CI持續整合/CD
網際網路電商購物車架構演變案例
網際網路業務場景下訊息佇列架構
網際網路高效研發團隊管理演進之一
訊息系統架構設計演進
網際網路電商搜尋架構演化之一
企業資訊化與軟體工程的迷思
企業專案化管理介紹
軟體專案成功之要素
人際溝通風格介紹一
精益IT組織與分享式領導
學習型組織與企業
企業創新文化與等級觀念
組織目標與個人目標
初創公司人才招聘與管理
人才公司環境與企業文化
企業文化、團隊文化與知識共享
高效能的團隊建設
專案管理溝通計劃
構建高效的研發與自動化運維
某大型電商雲平臺實踐
網際網路資料庫架構設計思路
IT基礎架構規劃方案一(網路系統規劃)
餐飲行業解決方案之客戶分析流程
餐飲行業解決方案之採購戰略制定與實施流程
餐飲行業解決方案之業務設計流程
供應鏈需求調研CheckList
企業應用之效能實時度量系統演變

如有想了解更多軟體設計與架構, 系統IT,企業資訊化, 團隊管理 資訊,請關注我的微信訂閱號:

image_thumb2_thumb_thumb_thumb_thumb[2]

作者:Petter Liu
出處:http://www.cnblogs.com/wintersun/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。 該文章也同時釋出在我的獨立部落格中-Petter Liu Blog。

相關文章