[入門]使用Openresty構建認證網關
在單體應用中, 我們可以通過 cookie + session, 或者 JSON web token, 將認證邏輯在單體應用中實現, 簡單高效, 還特別省事.
然而這幾年隨着服務化潮流越來越火(我覺得這是必然趨勢, 想想我們人類社會是如何運作的), 很多以前單體應用不存在的問題, 現在已成為對單體應用拆分過程中的第一個障礙, 比如系統的認證體系.
如果每個拆出來的服務都要做一次認證(就是程序員多寫幾份認證的代碼啦), 對於有理想有追求的靈魂の碼農來說, 是絕對無法接受的.你說認證代碼copy就好了, 不用重新寫.no no no, 這樣搞出來的架構不僅看着別扭, 代碼聞着就覺得臭, 而且遲早有一天會出問題.
解決單體應用拆分服務后的認證問題其實很常規, 回想下祖師爺們幫我們總結的一句話: "Any problem in computer science can be solved by another layer of indirection." 我們可以在所有服務前面增加一層認證服務.

看到認證服務這一層用來作為用戶請求的總入口, 有Nginx或者Apache使用經驗的同學自然而然就想到它們. 如果能把認證模塊的功能整合進Nginx或者Apache這些Web服務器, 那豈不是更完美 ?
而這篇文章的主角: Openresty,就可以幫助我們簡單快速得完成這個想法.這是一個由春哥(Github)發起的項目.你可以將Openresty看做Nginx + 常用模塊構成的軟件包, 但是最重要的功能是我們可以使用Lua在Nginx實現Web框架才能實現的邏輯, 接下來文章將會開始介紹如何使用Openresty, 將上面提到的認證服務整合進Nginx里.
安裝
Openresty有兩種安裝方式, 一種是使用源碼編譯安裝.一種使用官方提供的預編譯包:
具體可參考官網的安裝文檔
Hello world
如果你沒修改過Openresty的安裝位置, 默認會被安裝在/use/local/openresty目錄下.我們現在可以嘗試寫一個Hello world級別的demo.
為Openresty創建工作目錄並創建配置文件:
mkdir ~/openresty_work
cd ~/openresty_work
touch nginx.conf
接下來在nginx.conf里面配置一個路由規則
worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { server { listen 8080; location /hello { default_type text/html; content_by_lua_block { ngx.say("Hello Openresty.") } } } }
跟普通的Nginx配置文件比起來, 上面的配置多了一個content_by_lua_block指令, 正是通過調用該指令, 訪問該路由的時候,才會輸出相應的內容.這個指令是由Openresty中的LuaNginxModule模塊提供的功能, 請求進來的時候, Nginx會啟動lua的虛擬機, 輸出的內容則由lua提供.
我們可以使用content_by_lua_file
指令替代content_by_lua_block
, 將相關的lua代碼寫進文件里.
location /hello {
content_by_lua_file lua/hello.lua;
}
--- hello.lua ngx.say("Hello Openresty.")
有了上面的鋪墊,接下來可以開始構建我們的認證服務,認證的方式使用JWT
Openresty將一個請求的生命周期划分為4個階段:

我們的認證服務將會掛載在第二階段, 即 Rewrite/Access Phase 上.
接下來准備一個需要用到的庫:
lua-resty-jwt
clone下來后放到hello.lua文件所在的文件夾,並將lua_package_path配置為:
lua_package_path "/root/openresty_work/lua/?.lua;/root/openresty_work/lua/lua-resty-jwt/lib/?.lua;;";
構建的思路也很簡單, 對用戶提供一個登錄請求, 驗證身份后將jwt token分發給用戶.用戶接下來訪問需要認證的接口, 則在header里面加入該token, 請求進入Openresty后由lua提取出token進行認證.
Nginx配置文件
server {
listen 8080;
location /hello {
content_by_lua_file lua/hello.lua;
}
location /login {
content_by_lua_file lua/sign.lua;
}
location /service1 {
access_by_lua_file lua/verify.lua;
# 需要反向代理在這配置
}
location /service {
access_by_lua_file lua/verify.lua;
# ...
}
}
下面是配置中相關的lua文件
sign.lua ↓:
local jwt = require 'resty.jwt' -- 只允許POST請求 if ngx.req.get_method() ~= 'POST' then ngx.status = 405 ngx.say("Mehtod Not Allow") return end -- 獲取請求body ngx.req.read_body() local body_raw = ngx.req.get_body_data() local body_json = cjson.decode(body_raw) local username = body_json['username'] local password = body_json['password'] if not username or not password then ngx.log(ngx.ERR, username, password) ngx.status = 400 ngx.say('無法獲取賬號或者密碼') return end -- 驗證賬號和密碼是否正確,如果驗證失敗則做如下處理 if not this_is_a_auth_method(username, password) then ngx.status = 401 ngx.say('認證失敗') return end
verify.lua ↓:
local jwt = require 'resty.jwt'
-- 從請求中提取header並從header從獲取token字段
local headers = ngx.req.get_headers()
local token = headers['token']
-- 檢查token是否存在
if not token then
ngx.status = 400
ngx.say('無法獲取token')
return
end
-- 驗證token
local jwt_obj = jwt:verify(vars.jwt_salt(), token)
if not jwt_obj['verified'] then
ngx.status = 401
ngx.say('無效的token')
return
end
至此一個使用Openresty構建的認證網關的雛形已經出來了.需要說明的一句是, 上面的代碼由於沒有公司相關的運行環境,筆者沒有經過測試和驗證.所以只可閱讀,不可復制后直接運行 :)
如果想把這套認證網關用在生產環境上, 還有很多東西需要考慮.比如跨域問題, 靜態文件的代理問題等等.
個人接觸Openresty的時間也不長, 文中難免會有地方寫錯了或者表達得很差, 歡迎發評論或者發郵件給我指正: lwhile521@gmail.com ,感謝.
對於Openresty, 個人認為要對它產生興趣,關鍵在於認不認可讓Nginx承擔除了Web服務器之外更多的業務, 對於Openresty, 它能帶來的好處有:
-
極致的性能.上文沒有提到Openresty的性能, 其實Openresty的編程模型和NodeJS很像, 在Openresty的世界里面,所有東西都是非阻塞的,更難得可貴的是, 它不需要使用NodeJS中的回調函數, 代碼寫起來其實還是同步模型, 配合C語言編寫的Nginx, 最快的腳本語言lua+luajit解釋器,這套方案的性能無可挑剔了.
-
降低了Nginx模塊的開發難度. Nginx + C/C++能做的, Openresty用lua都能做.開發效率高了, 性能還不怎么降, 何樂而不為呢?