如果你对网关kong是一片空白,建议先看我写的安装部署方案,
https://blog.csdn.net/fj56355113/article/details/107613497
开发初期需要对网关插件的原理有一些了解:
1、OpenResty:OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。具体查阅网站:http://openresty.org/cn/
2、Lua:是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。不要说你不会,哥也就花了1~2天的时候上车了。
3、Nginx:nginx我不想多说了,我只想说kong其实是在nginx的基础上做了一些API的管理,然后开放了一些不错的插件;
4、constants.lua:kong安装完成后,会默认在/usr/local/share/lua/5.1/kong路径下把核心的插件初始化,该constants.lua是定义所有插件的主入口,我们新增一个插件都要在这个文件中声明变量;
插件具体位置在/usr/local/share/lua/5.1/kong/plugins
本次举例采用插件名为extend-auth为例,所以在plugins中创建extend-auth文件夹,以下为详细举例:
插件的主要构成部分有handler.lua、schema.lua,这是游戏规则,不要问为什么。
1、schema.lua是插件在使用时配定义参数的主要脚本文件
例如:konga中找到自己的插件,需要使用时,
以下每一个参数都是要定义出来,具体看代码:
schema.lua详细代码如下:
-- extend-auth.schema.lua
local typedefs = require "kong.db.schema.typedefs"
return {
name = "extend-auth",
fields = {
{ consumer = typedefs.no_consumer },
{ config = {
type = "record",
fields = {
{ token_source_enums = { type = "set", elements = { type = "string" } } },
{ check_ip_url = typedefs.url({ required = true }) },
{ base_code = { type = "string", required = true, default = "DATA_CENTER" } },
{ app_key = { type = "string", required = true } },
{ secret_key = { type = "string", required = true } },
{ ext_url_path_enums = { type = "set", elements = { type = "string" } } }
},
},
},
}
}
2、handler.lua是插件被使用后,请求拦截后进入的主要方法;
local BasePlugin = require "kong.plugins.base_plugin"
local jwt_decoder = require "kong.plugins.extend-auth.jwt_parser"
local authHttp = require "kong.plugins.extend-auth.auth_http"
-- 字符串拆分函数
function Split(szFullString, szSeparator)
local nFindStartIndex = 1
local nSplitIndex = 1
local nSplitArray = {}
while true do
local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex)
if not nFindLastIndex then
nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString))
break
end
nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, nFindLastIndex - 1)
nFindStartIndex = nFindLastIndex + string.len(szSeparator)
nSplitIndex = nSplitIndex + 1
end
return nSplitArray
end
local CustomHandler = BasePlugin:extend()
CustomHandler.VERSION = "1.0.0"
CustomHandler.PRIORITY = 10
function CustomHandler:new()
CustomHandler.super.new(self, "extend-auth")
end
function CustomHandler:access(config)
-- 第一步:URL在配置范围内跳过校验(针对登录或者其它非授权页面)
local urlPath = kong.request.get_path()
if config.ext_url_path_enums ~= nil then
for _, v in ipairs(config.ext_url_path_enums) do
if v == urlPath then
return
end
end
end
-- 第二步:获取Cookies中【tokenPre】令牌来源值
local tokenPre = ngx.var.cookie_tokenPre
-- 第三步:解析令牌来源,获取值如果是非内部令牌,非内部系统的范围值通过kong配置的枚举,跳过下面所有逻辑,由各个业务集成认证包去校验
if tokenPre == nil then
return kong.response.exit(401, "【tokenPre】不在设定范围内NULL", { ["Content-Type"] = "application/json;charset=UTF-8" } )
end
if tokenPre ~= "inside" and config.token_source_enums ~= nil then
for _, v in ipairs(config.token_source_enums) do
if v == tokenPre then
return
end
end
return kong.response.exit(401, "【tokenPre】不在设定范围内", { ["Content-Type"] = "application/json;charset=UTF-8" } )
end
-- 第四步:获取Cookies中【accessToken】访问令牌值
local accessToken = ngx.var.cookie_accessToken
if accessToken == nil then
return kong.response.exit(401, "【accessToken】认证令牌失效", { ["Content-Type"] = "application/json;charset=UTF-8" } )
end
-- 第五步:解析访问令牌获取user_id,拿到user_id的值,该值的组成结构【INSIDE_B_819554】,如果是以【INSIDE_B_】开头则不进行URL校验逻辑
local jwtAccessToken, err = jwt_decoder:new(accessToken)
if err ~= nil then
return kong.response.exit(401, "【accessToken】认证令牌失效,原因:" .. err, { ["Content-Type"] = "application/json;charset=UTF-8" } )
end
if jwtAccessToken.claims == nil or jwtAccessToken.claims.user_id == nil or
jwtAccessToken.claims.client_id == nil or jwtAccessToken.claims.expires_in == nil then
return kong.response.exit(401, "【accessToken】认证令牌信息为空", { ["Content-Type"] = "application/json;charset=UTF-8" } )
end
local accessToken = jwtAccessToken.claims
local clientId = accessToken.client_id
local expiresIn = accessToken.expires_in
local userArray = Split(accessToken.user_id, "_")
if userArray ~= nil and userArray[1] ~= nil and userArray[2] ~= nil and userArray[3] ~= nil then
if userArray[1] == "SEGI" and userArray[2] == "B" then
-- 第六步:获取当前URL,问号以后的直接剔除
local userId = userArray[3]
-- 第七步:用user_id和URL去请求数据中心开放出来的API接口
local checkIpUrl = config.check_ip_url
local requestString = "{\"data\":{\"userId\":" .. userId .. ", \"url\":\"" .. urlPath .. "\", \"nOrgId\":0, \"channel\":1 }}"
local respData, httpErr = authHttp.http_post_client(checkIpUrl, requestString, 5000)
if httpErr ~= nil then
return kong.response.exit(401, "【URL鉴权】校验异常" .. httpErr .. ",原因:" .. err, { ["Content-Type"] = "application/json;charset=UTF-8" } )
else
if respData ~= "0" then
return kong.response.exit(401, "【URL鉴权】校验不通过respData:" .. respData, { ["Content-Type"] = "application/json;charset=UTF-8" } )
end
end
end
else
return kong.response.exit(401, "【accessToken】认证令牌解析有误", { ["Content-Type"] = "application/json;charset=UTF-8" } )
end
end
return CustomHandler
该实现的逻辑是根据我这边业务场景而写,里面具体内容可以不用关注,主要是给大家关注,kong的自定义插件怎么写。
local BasePlugin = require "kong.plugins.base_plugin" # kong的基础类,所有自定义插件都是这样引入
local jwt_decoder = require "kong.plugins.extend-auth.jwt_parser" # 为jwt解析类,可自己百度去找
local authHttp = require "kong.plugins.extend-auth.auth_http" # 封装的HTTP请求第三方服务校验的客户端方法
注意几点:
1、每次插件写完后,需要重启kong,重启命令: kong restart -c /etc/kong/kong.conf --vv
2、如果插件中有语法错误,重启会失败,可以查阅日志找问题,日志位置在/usr/local/kong/logs/error.log