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.requestkong.log kong.version 当前 Kong 节点的版本号,字符串格式 print(.
本文最初于 2020 年 9 月在公司内部发表,现整理并增加部分批注公开发布。 最开始加入公司 Infrastructure 团队时,迷茫的我接到的的一个任务就是学习 Lua 和 OpenResty,当时收到了两本书籍的 PDF 文件,要求尽快理解学习,能够掌握 Kong,并且具有研发能力。 当时我还没有怎么接触开源社区,能力只停留在 Git Clone,大概花了 2 周时间,我学习 Lua 基本语法后,开始阅读 Kong 项目的源码,并找到几个切入点梳理了源码分析文档,也应该正是这个成果让组长认同了我,这