在完成API網關的一系列部署和配置以後,下一步在系統上須要對應用程序疊加自定義的插件,主要用於認證與鑑權邏輯。Kong社區版自己集成了衆多的插件,其中也包括認證相關的oauth二、jwt等插件,但使用的時候須要和kong內部的consumer結合,也就意味着應用系統設計上須要和kong的數據庫進行交互。nginx
對應用系統而言,早期網關功能由nginx來實現,其認證鑑權的業務邏輯由應用系統自身來實現,微服務改造以後,但願網關層可以承擔起認證與鑑權的角色,鑑於此,咱們決定採用自定義插件的形式來實現微服務的認證與鑑權。redis
1、插件的目錄規範數據庫
在插件目錄下必須至少存在handler.lua和schema.lua兩個文件。
其餘可選的文件有api.lua,daos.lua、migrations/.lua等
其中handler.lua用於實現業務路徑,schema.lua用於用戶自定義配置
api.lua用於定義admin api,若是須要使用kong的內置數據庫對象,還應當存在daos.lua文件,migrations/.lua用於數據遷移相關json
2、插件的加載後端
在主配置文件中須要聲明加載自定義插件的名稱,如自定義插件不存在默認位置則須要配置路徑
插件默認路徑爲:/usr/local/share/lua/5.1/kong/plugins/api
參考:https://docs.konghq.com/enterprise/2.3.x/plugin-development/file-structure/跨域
對於HTTP/HTTPS 請求,涉及的請求生命週期上下文及函數
:init_worker()函數對應init_worker階段,即每次Nginx worker進程啓動時執行
:certificate()函數對應ssl_certificate階段,即在SSL握手的SSL證書服務階段執行
:rewrite()函數對應rewrite階段,即在每一個請求的重寫階段執行
:access()函數對應access階段,即在每一個請求被代理到上游服務以前執行
:header_filter()函數對應header_filter階段,當已從上游服務接收到全部響應頭字節時執行
:body_filter()函數對應body_filter階段對從上游服務接收到的響應主體的每一個塊執行
:log()函數對應log階段最後一個響應字節已發送到客戶端時執行 併發
鑑權和認證的業務邏輯須要放在access階段app
參考:https://docs.konghq.com/enterprise/2.3.x/plugin-development/custom-logic/ide
1、handler.lua
--引用包 local redis = require "resty.redis" local cjson = require "cjson.safe" local plugin = { PRIORITY = 1000, VERSION = "0.1", } -- 讀取redis值函數 local function redis_get(conf,key) -- 鏈接redis local red = redis:new() red:set_timeout(conf.redis_conn_timeout) local conn_ok, conn_err = red:connect(conf.redis_ip, conf.redis_port) red:auth(conf.redis_password) if not conn_ok then kong.response.exit(500,{message = "redis鏈接失敗: "..conn_err}) end --調用hget獲取值 local get_ok, get_err = red:get(key) --若是hget未獲取到 if not get_ok then kong.response.exit(500,{message = "redis獲取"..key.."失敗: "..get_err}) end --鏈接池默認100個,默認超時時間60s local keep_ok, keep_err = red:set_keepalive(conf.redis_pool_timeout, conf.redis_pool_size) if not keep_ok then kong.response.exit(500,{message = "redis鏈接池設置失敗: "..keep_err}) end return get_ok end --base64解碼App-Authentication,返回解碼後的json local function decode_appauth(app_auth) --去掉Basic字符 local app_64 = string.gsub(app_auth,"Basic ",'') --使用base64解密 local app = ngx.decode_base64(app_64) if not app then kong.response.exit(401,{message = "App-Authentication 解密失敗"}) end --轉換爲json結構 local json_ok,json_err = cjson.decode(app) if not json_ok then kong.response.exit(401,{message = "App-Authentication json解析失敗: "..json_err}) end return json_ok end --生成網關用戶gateway_user頭 local function generate_gateway_user(conf,token) if token == nil then return nil end --生成當前用戶信息數據的redis key名 local current_user_key = "auth_to_user_info:"..string.gsub(token,"Bearer ",'') local current_user_value = redis_get(conf,current_user_key) local current_user_json = cjson.decode(current_user_value) --從用戶信息數據的redis中解析出來,並拼湊gateway_user local gateway_user if current_user_value ~= ngx.null and current_user_json then gateway_user=cjson.encode({ ["platformId"] = current_user_json.platformId, ["platformVersionId"] = current_user_json.platformVersionId, ["projectId"] = current_user_json.projectId, ["subProjectId"] = current_user_json.subProjectId, ["unitId"] = current_user_json.unitId, ["organizationId"] = current_user_json.organizationId, ["userId"] = current_user_json.userId, ["username"] = current_user_json.username, }) end return gateway_user end function plugin:access(plugin_conf) --請求爲OPTIONS不獲取請求頭,直接跳過 if kong.request.get_method() == "OPTIONS" then return end --獲取請求頭 local app_auth = kong.request.get_header("App-Authentication") local token = kong.request.get_header("Authorization") if app_auth == nil then kong.response.exit(401,{message = "請求頭缺失"}) end --解析App-Authentication請求頭,獲取平臺ID等內容的json local app_json = decode_appauth(app_auth) --網關上下文,從App-Authentication中解析出來,並拼湊gateway_context local gateway_context=cjson.encode({ ["platformId"] = app_json.platformId, ["platformVersionId"] = app_json.platformVersionId, ["projectId"] = app_json.projectId, ["subProjectId"] = app_json.subProjectId, }) --生成用戶信息數據gateway_user local gateway_user = generate_gateway_user(plugin_conf,token) ngx.req.set_header("Gateway-Context",gateway_context) ngx.req.set_header("Gateway-User",gateway_user) end return plugin
2、schema.lua
local typedefs = require "kong.db.schema.typedefs" return { name = "gateway", fields = { { protocols = typedefs.protocols_http }, { config = { type = "record", fields = { { redis_ip = typedefs.ip({ required = true }) }, { redis_port = typedefs.port({ required = true }) }, { redis_password = { type = "string", default = "Please input redis password" }, }, { redis_conn_timeout = typedefs.timeout({ required = true ,default = 1000,}) }, { redis_pool_timeout = typedefs.timeout({ required = true ,default = 60000,}) }, { redis_pool_size = { type = "number", default = 100, } }, { jwt_signature = { type = "string", default = "Please input jwt signature", }, }, { sso_url = typedefs.url({ required = true }) }, }, }, }, }, }
3、說明
初版的自定義網關插件不涉及認證與鑑權業務邏輯,插件工做邏輯以下
一、判斷若是請求方法爲OPTIONS則直接轉發,對應跨域相關的請求
二、獲取請求頭中的App-Authentication、Authorization字段
三、若是App-Authentication請求頭不存在,返回狀態碼401
四、根據獲取到的App-Authentication請求頭信息,進行base64解析,最終拼接成gateway_context網關上下文
五、根據獲取到的Authorization請求頭信息,從redis中獲取數據,最終拼接成gateway_user網關上下文
六、轉發到後端的微服務,並附帶gateway_context、gateway_user請求頭
最終認證與鑑權版本自定義網關插件的業務邏輯較爲複雜,且涉及具體業務邏輯,所以不進行展現和說明。
因爲kong採用k8s方式部署,所以配置文件咱們採用configmap外掛形式實現,因爲插件目前還沒有進入穩定階段,需求變動相對頻繁,因此暫定插件目錄採起PVC外掛的形式實現,插件代碼更新不須要進行鏡像編譯。同時考慮到代碼bug,插件大併發下的性能問題,咱們插件分爲鑑權和不鑑權兩個版本,配置在微服務的route裏面,如出現性能或bug問題,能夠在konga面板上快速禁用和啓用,避免形成大面積的故障。
一、系統配置文件
grep 'plugins' kong.conf |grep -v '#' plugins = bundled,gateway,gateway-auth grep 'lua_package_path' kong.conf |grep -v '#' lua_package_path = ./?.lua;./?/init.lua;/mnt/mfs/?.lua;;
二、將配置文件導入爲configmap
kubectl create cm kong-conf -n kong --from-file=kong.conf
三、工做負載聲明文件中引用插件目錄和configmap
volumes: - name: vol-localtime hostPath: path: /etc/localtime type: '' - name: mfsdata persistentVolumeClaim: claimName: mfsdata-kong - name: kong-conf configMap: name: kong-conf items: - key: kong.conf path: kong.conf volumeMounts: - name: vol-localtime readOnly: true mountPath: /etc/localtime - name: mfsdata mountPath: /mnt/mfs - name: kong-conf mountPath: /etc/kong/kong.conf subPath: kong.conf