在完成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