limit_second:有效时间(限制客户端一直重复请求)
inner_host:内网host (配置了则不需要验签以及解密)
local typedefs = require "kong.db.schema.typedefs"
return {
name = "api-safety",
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{limit_second = {type = "number", required = true},},
{inner_host = {type = "array", elements = {type = "string"}}}
}, }, },
access.lua
local safety = require "safety_lua" -- 加解密
--local version = require "version"
local multipart = require "multipart"
local cjson = require "cjson"
local pl_tablex = require "pl.tablex"
local math = require "math"
local kong = kong
local table_insert = table.insert
local get_uri_args = kong.request.get_query
local set_uri_args = kong.service.request.set_query
local clear_header = kong.service.request.clear_header
local get_header = kong.request.get_header
local set_header = kong.service.request.set_header
local clear_header = kong.service.request.clear_header
local get_headers = kong.request.get_headers
local set_headers = kong.service.request.set_headers
local set_method = kong.service.request.set_method
local get_method = kong.request.get_method
local get_raw_body = kong.request.get_raw_body
local set_raw_body = kong.service.request.set_raw_body
local encode_args = ngx.encode_args
local ngx_decode_args = ngx.decode_args
local type = type
local str_find = string.find
local pcall = pcall
local pairs = pairs
local error = error
local rawset = rawset
local pl_copy_table = pl_tablex.deepcopy
local _M = {}
local DEBUG = ngx.DEBUG
local CONTENT_LENGTH = "content-length"
local CONTENT_TYPE = "content-type"
local HOST = "host"
local JSON, MULTI, ENCODED = "json", "multi_part", "form_encoded"
local EMPTY = pl_tablex.readonly({})
local function parse_json(body)
if body then
local status, res = pcall(cjson.decode, body)
if status then
return res
local function decode_args(body)
if body then
return ngx_decode_args(body)
return {}
local function get_content_type(content_type)
if content_type == nil then
return
if str_find(content_type:lower(), "application/json", nil, true) then
return JSON
elseif str_find(content_type:lower(), "multipart/form-data", nil, true) then
return MULTI
elseif str_find(content_type:lower(), "application/x-www-form-urlencoded", nil, true) then
return ENCODED
local function in_array(val,list)
if not list then
return false
if list then
for _, v in pairs(list) do
if v == val then
return true
local function table_sort_by_key(t)
local a = {}
for n in pairs(t) do
a[#a+1] = n
table.sort(a)
local i = 0
return function()
i = i + 1
return a[i], t[a[i]]
local function table_remove_by_key(tbl,key)
local tmp ={}
for i in pairs(tbl) do
table.insert(tmp,i)
local newTbl = {}
local i = 1
while i <= #tmp do
local val = tmp [i]
if val == key then
table.remove(tmp,i)
newTbl[val] = tbl[val]
i = i + 1
return newTbl
local function decrypt_and_verify(conf, params, secret)
--拿到服务器密钥
new_secret = rsa.decrypt(secret)
local decrypt_params = safety.decrypt(params, new_secret)
if decrypt_params == nil then
return -401,{},"Decrypt error"
local new_parameters = parse_json(decrypt_params)
if new_parameters == nil then
return -402,{},"Incorrect data format"
local timestamp = new_parameters["timestamp"]
if timestamp == nil then
timestamp = 0
local between_secend = ngx.now() - timestamp/1000
if math.abs(between_secend) > conf.limit_second then
return -403,{},"Request time is invalid"
local sign = new_parameters['token']
if sign == nil then
return -404,{},"Sign is empty"
new_parameters = table_remove_by_key(new_parameters, 'token')
local params_str = ''
for _,v in table_sort_by_key(new_parameters) do
params_str = params_str .. v
local check_sign = safety.signature(params_str)
if sign ~= check_sign then
return -405,{},"Sign is error"
return 200, new_parameters, ""
local function transform_querystrings(conf, header)
local query_string = get_uri_args()
local params = query_string["params"]
if params == nil or params == '' then
return -400,{},"Params is required"
local err_code,parameters,err_msg = decrypt_and_verify(conf, params, header['Secret'])
if err_code ~= 200 then
if err_code == -403 then
return kong.response.exit(480, { code = -480, data = {}, msg = err_msg })
return kong.response.exit(200, { code = err_code, data = {}, msg = err_msg })
set_uri_args(parameters)
local function transform_json_body(conf, body, content_length, header)
local content_length = (body and #body) or 0
local table_params = parse_json(body)
if content_length == 0 then
return -400,{},"Body is empty"
local params = table_params["params"]
if params == nil or params == '' then
return -400,{},"Params is required"
local err_code,parameters,err_msg = decrypt_and_verify(conf, params, header['Secret'])
if err_code ~= 200 then
return err_code,parameters,err_msg
return 200, cjson.encode(new_parameters), ""
local function transform_url_encoded_body(conf, body, content_length, header)
local table_params = decode_args(body)
if content_length == 0 then
return -400,{},"Body is empty"
local params = table_params["params"]
if params == nil or params == '' then
return -400,{},"Params is required"
local err_code,parameters,err_msg = decrypt_and_verify(conf, params)
if err_code ~= 200 then
return err_code,parameters,err_msg
return 200, encode_args(parameters), ""
local function transform_multipart_body(conf, body, content_length, content_type_value)
if content_length == 0 then
return -400,{},"Body is empty"
local mul_parameters = multipart(body and body or "", content_type_value)
local params = mul_parameters:get("params").value
if params == nil or params == '' then
return -400,{},"Params is required"
local err_code,parameters,err_msg = decrypt_and_verify(conf, params, header['Secret'])
if err_code ~= 200 then
return err_code,parameters,err_msg
mul_parameters:delete("params")
for key, value in pairs(parameters) do
mul_parameters:set_simple(key, value)
return 200, mul_parameters:tostring(), ""
local function transform_body(conf, header)
local content_type_value = get_header(CONTENT_TYPE)
local content_type = get_content_type(content_type_value)
if content_type == nil then
return
local body = get_raw_body()
local content_length = (body and #body) or 0
local msg = ""
local error_code = 200
if content_type == ENCODED then
error_code, body, msg = transform_url_encoded_body(conf, body, content_length, header)
elseif content_type == MULTI then
error_code, body, msg = transform_multipart_body(conf, body, content_length, content_type_value)
elseif content_type == JSON then
error_code, body, msg = transform_json_body(conf, body, content_length, header)
if error_code == 200 then
set_raw_body(body)
set_header(CONTENT_LENGTH, #body)
if error_code == -403 then
return kong.response.exit(480, { code = -480, data = {}, msg = msg })
return kong.response.exit(200, { code = error_code, data = {}, msg = msg })
function _M.execute(conf)
clear_header("Need-Sign") --防止外部输入Need-Sign的头,绕过验签
local host = kong.request.get_host()
local headers = get_headers()
if in_array(host, conf.inner_host) then
set_header("Need-Sign", 0) --内网域名不需要验签
--local ctx = ngx.ctx
local method = kong.request.get_method()
if method == "POST" then
transform_body(conf, headers)
elseif method == "GET" then
transform_querystrings(conf, headers)
return
return _M
body_filter.lua
local safety = require "safety_lua"
local cjson = require "cjson"
local concat = table.concat
local str_find = string.find
local CONTENT_LENGTH = "content-length"
local CONTENT_TYPE = "content-type"
local JSON, MULTI, ENCODED = "json", "multi_part", "form_encoded"
local _M = {}
local function get_content_type(content_type)
if content_type == nil then
return
if str_find(content_type:lower(), "application/json", nil, true) then
return JSON
elseif str_find(content_type:lower(), "multipart/form-data", nil, true) then
return MULTI
elseif str_find(content_type:lower(), "application/x-www-form-urlencoded", nil, true) then
return ENCODED
local function parse_json(body)
if body then
local status, res = pcall(cjson.decode, body)
if status then
return res
local function transform_body()
local ctx = ngx.ctx
local chunk, eof = ngx.arg[1], ngx.arg[2]
ctx.rt_body_chunks = ctx.rt_body_chunks or {}
ctx.rt_body_chunk_number = ctx.rt_body_chunk_number or 1
if eof then
local chunks = concat(ctx.rt_body_chunks)
local body = chunks
if ctx.use_flag == true then
if parse_json(body) == nil then
--后端返回非json数据,不进行加密
ngx.arg[1] = body
ngx.arg[1] = safety.encrypt(body, secret)
ngx.arg[1] = body
ctx.rt_body_chunks[ctx.rt_body_chunk_number] = chunk
ctx.rt_body_chunk_number = ctx.rt_body_chunk_number + 1
ngx.arg[1] = nil
function _M.execute()
transform_body()
return _M
header_filter.lua
ps:因为加密了,所以content-length会改变,切记需要删除!!!
local kong = kong
local math = require "math"
local _M = {}
local function transform_header()
local ctx = ngx.ctx
ngx.update_time()
local systemTime = math.ceil(ngx.now() * 1000)
if ctx.use_flag == true then
kong.response.set_header("Content-Type", "text/plain")
kong.response.set_header("Encrypt-Flag", 1)
kong.response.clear_header("Content-Length")
kong.response.set_header("Encrypt-Flag", 0)
kong.response.set_header("System-Time", systemTime) -- 返回系统时间,用于客户端校正时间
function _M.execute()
transform_header()
return _M
handler.lua
local access = require "kong.plugins.api-safety.access"
local body_filter = require "kong.plugins.api-safety.body_filter"
local header_filter = require "kong.plugins.api-safety.header_filter"
local SafetyHandler = {}
-- 请求时的处理过程
function SafetyHandler:access(conf)
access.execute(conf)
function SafetyHandler:header_filter()
header_filter.execute()
function SafetyHandler:body_filter()
body_filter.execute()
-- PRIORITY 越大执行顺序越靠前
SafetyHandler.PRIORITY = 800
SafetyHandler.VERSION = "1.0.0"
return SafetyHandler
kong自定义插件发送http请求,并解析响应json数据
公司有这方面要求,就用kong+lua写了一个类似http客户端,不涉及具体业务,给有需要的同学参考。
项目已托管github,链接如下:
https://github.com/MyRong12138/http-service
http-service
kong自定义插件实现发送http post请求,并解析返回数据
添加插件
进入/us...
Kong需要监听80端口,或由监听80端口的负载均衡器代理。
lua_ssl_trusted_certificate需要在设置kong.conf以确保插件可以正确验证咱们加密API。 的CA-束文件通常是/etc/ssl/certs/ca-certificates.crt为Ubuntu / Debian和/etc/ssl/certs/ca-bundle.crt为CentOS / Fedora的/ RHEL。 如果将Kong与Docker一起使用,则还可以将KONG_LUA_SSL_TRUSTED_CERTIFICATE设置为环境,而不用更改kong.conf 。
启用插件
对于每个需要证书的域,请确保将DOMAI
Kong是一个api网关,在客户端和服务间转发API进行通信,支持自定义插件来扩展功能。
Kong 是在Nginx基础上构建的,更确切地说,kong是在Nginx中运行的Lua应用程序,由 lua-nginx-module实现。
Kong和OpenResty一起发行的,其中已经包含了lua-nginx=module。其中OpenResty不是Nginx的分支,而是...
前面洋洋洒洒写了那么多文章,Kong搭建、Konga搭建、Kong插件开发工具包、Lua算法实现等等,就为了这篇Kong插件开发铺垫,在进一步讨论之前,有必要再简要阐述下 Kong 是如何构建的,特别是它如何与 Nginx 集成,以及它与 Lua 脚本之间的关系。
使用 lua-nginx-module 模块可以在 Nginx 中启用 Lua 脚本功能,Kong 与 OpenResty 一起发布,OpenResty 中已经包.
kong插件应用(熔断 限流,黑白名单,认证(basic,key,jwt,hmac,),授权,加密,zipkin链路跟踪,日志, prometheus可视化, 爬虫控制插件)
插件概述
插件之于kong,就像Spring中的aop功能。
在请求到达kong之后,转发给后端应用之前,你可以应用kong自带的插件对请求进行处理,合法认证,限流控制,黑白名单校验,日志采集等等。同时,你也可以按照kong的教程文档,定制开发属于自己的插件。
kong的插件分为开源版和社区版,社区版还有更多的定制功能,但是社区版是要收费的。
目前,KONG开源版本一共开放28个插件,如下:
ac...
Kong插件开发工具包
插件开发工具包(或称 PDK),是一组 Lua 方法和变量,插件可以使用这些方法和变量实现自己的逻辑,PDK 最初在 Kong 0.14.0 中发布,PDK 保证从1.0.0版本开始向前兼容,截至本版本,PDK 尚未达到1.0.0,然而插件作者可以放心依赖它与请求、响应或核心组件进行安全可靠的交互
用户可以通过全局变量访问插件开发工具包,例如kong.request、kong.log
kong.version
当前 Kong 节点的版本号,字符串格式
print(.
本文最初于 2020 年 9 月在公司内部发表,现整理并增加部分批注公开发布。
最开始加入公司 Infrastructure 团队时,迷茫的我接到的的一个任务就是学习 Lua 和 OpenResty,当时收到了两本书籍的 PDF 文件,要求尽快理解学习,能够掌握 Kong,并且具有研发能力。
当时我还没有怎么接触开源社区,能力只停留在 Git Clone,大概花了 2 周时间,我学习 Lua 基本语法后,开始阅读 Kong 项目的源码,并找到几个切入点梳理了源码分析文档,也应该正是这个成果让组长认同了我,这