Kongはカスタム認証プラグインを開発

この記事は、金の創造への道を始めるための「新人創造セレモニー」イベントに参加しました。

この記事では、主にkongゲートウェイプラグインの記述、使用するライブラリ、およびプラグインファイルの構造を紹介します。テストケースでは、単純なカスタム権限認証プラグインの開発を採用しています。参照公式ウェブサイトアドレスは

1.ファイル構造の概要

  1. 単純なプラグインのファイル構造は次のとおりです。
simple-plugin
├── handler.lua
└── schema.lua
复制代码
  1. より複雑な機能を備えたプラグインファイルの構造は次のとおりです。
complete-plugin
├── api.lua
├── daos.lua
├── handler.lua
├── migrations
│   ├── init.lua
│   └── 000_base_complete_plugin.lua
└── schema.lua

复制代码
  1. フィールド説明
名前 必要ですか 説明
api.lua いいえ プラグインエンティティ自体と対話するためにUI管理インターフェイスから呼び出すことができるリストを定義します。
daos.lua いいえ データインタラクションに関連するファイルは、プラグインがデータベースを使用するときに使用されるデータベースインタラクション情報を定義します。
handler.lua はい プラグインのコアロジックは、Kongのリクエストと接続でさまざまな操作を実装するインターフェースです。
migrations*/*.lua いいえ daos.luaで使用するために、テーブルの作成など、いくつかのデータベーススクリプト操作を定義します。
schema.lua はい プラグインに必要なフィールド構成情報と、いくつかのフィールド検証操作を構成します。

2.ユーザー定義のロジックインターフェースの紹介

ユーザー定義のロジック、主handler.luaschema.luaこれら2つのファイルの一部のロジックは、開発プロセス中に公式テンプレートgithub.com/Kong/kong-p…を使用して開発できます。コードをプルダウンすると、プロジェクトの構造は次のようになります。

image.pnghandler.luaファイルの構造は次のとおりです。

local CustomHandler = {
  VERSION  = "1.0.0",
  PRIORITY = 10,
}

function CustomHandler:init_worker()
  -- Implement logic for the init_worker phase here (http/stream)
  kong.log("init_worker")
end


function CustomHandler:preread(config)
  -- Implement logic for the preread phase here (stream)
  kong.log("preread")
end


function CustomHandler:certificate(config)
  -- Implement logic for the certificate phase here (http/stream)
  kong.log("certificate")
end

function CustomHandler:rewrite(config)
  -- Implement logic for the rewrite phase here (http)
  kong.log("rewrite")
end

function CustomHandler:access(config)
  -- Implement logic for the rewrite phase here (http)
  kong.log("access")
end

function CustomHandler:header_filter(config)
  -- Implement logic for the header_filter phase here (http)
  kong.log("header_filter")
end

function CustomHandler:body_filter(config)
  -- Implement logic for the body_filter phase here (http)
  kong.log("body_filter")
end

function CustomHandler:log(config)
  -- Implement logic for the log phase here (http/stream)
  kong.log("log")
end

-- return the created table, so that Kong can execute it
return CustomHandler

复制代码

各メソッドは異なる段階で実行されます。必要に応じて対応するメソッドを実装できます。次のメソッドは、これらのメソッドに焦点を当てています。

メソッド名 リンク 説明
init_worker init_worker 各Nginxワークフローの開始時にのみ実行されます。
certificate ssl_certificate SSLハンドシェイクのSSL証明書サービスフェーズ中に実行されます。
rewrite リライト 書き換えフェーズハンドラーとして、クライアントから受信した各要求で実行されます。このフェーズでは、「サービス」も「コンシューマー」も識別されないため、このハンドラーは、プラグインがグローバルプラグインとして構成されている場合にのみ実行されます。
access アクセス プロキシされる前に、クライアントからのリクエストごとに実行されます
response アクセス 「header_filter()」と「body_filter()」に置き換えられました。アップストリームサービスから応答全体を受信した後、クライアントに送信する前に実行されます。
header_filter header_filter すべての応答ヘッダーバイトがアップストリームサービスから受信されたときに実行されます。
body_filter body_filter アップストリームサービスから受信した応答本文のチャンクごとに実行されます。応答がクライアントにストリーミングされると、バッファサイズを超える可能性があり、チャンクごとにストリーミングされます。応答が大きい場合、この関数は複数回呼び出すことができます。詳細については 、 lua-nginx-moduleを参照してください 。
log ログ 最後の応答バイトがクライアントに送信されたときに実行されます。

3.カスタムプラグインを開発します

  1. プラグインのアイデア

konga管理インターフェースによって要求された認証サーバーは、パスを認証するように構成されています。構成情報には、主に認証サーバーのエンドポイント、要求メソッド、要求キー、およびその他の情報が含まれます。

image.png

image.png

  1. プラグイン実装コード
    1. handler.lua
local httpUtil = require "resty.http"
local table_clear = require "table.clear"
local url = require "socket.url"
local cjson = require "cjson"
local encode_base64 = ngx.encode_base64
local pairs = pairs
local tonumber = tonumber
local fmt = string.format
local tostring = tostring

local queues = {} -- one queue per unique plugin config
local parsed_urls_cache = {}
local headers_cache = {}
local params_cache = {
  ssl_verify = false,
  headers = headers_cache,
}

local SelfPermissionHandler = {
  PRIORITY = 1000, -- set the plugin priority, which determines plugin execution order
  VERSION = "0.1", -- version in X.Y.Z format. Check hybrid-mode compatibility requirements.
}

local function check_customerKey(customerKey)
  local ok
  local err
  return ok, err;
end


-- Parse host url.
-- @param `url` host url
-- @return `parsed_url` a table with host details:
-- scheme, host, port, path, query, userinfo
local function parse_url(host_url)
  local parsed_url = parsed_urls_cache[host_url]

  if parsed_url then
    return parsed_url
  end

  parsed_url = url.parse(host_url)
  if not parsed_url.port then
    if parsed_url.scheme == "http" then
      parsed_url.port = 80
    elseif parsed_url.scheme == "https" then
      parsed_url.port = 443
    end
  end
  if not parsed_url.path then
    parsed_url.path = "/"
  end

  parsed_urls_cache[host_url] = parsed_url

  return parsed_url
end
-- Sends the provided payload (a string) to the configured plugin host
-- @return true if everything was sent correctly, falsy if error
-- @return error message if there was an error
local function send_payload(self, conf, payload)
  local method = conf.method
  local timeout = conf.timeout
  local keepalive = conf.keepalive
  local content_type = conf.content_type
  local http_endpoint = conf.http_endpoint

  local parsed_url = parse_url(http_endpoint)
  local host = parsed_url.host
  local port = tonumber(parsed_url.port)

  local httpc = httpUtil.new()
  httpc:set_timeout(timeout)

  table_clear(headers_cache)
  if conf.headers then
    for h, v in pairs(conf.headers) do
      headers_cache[h] = v
    end
  end
  headers_cache["Host"] = parsed_url.host
  headers_cache["Content-Type"] = content_type
  headers_cache["Content-Length"] = #payload
  if parsed_url.userinfo then
    headers_cache["Authorization"] = "Basic " .. encode_base64(parsed_url.userinfo)
  end

  params_cache.method = method
  params_cache.body = payload
  params_cache.keepalive_timeout = keepalive

  local url = fmt("%s://%s:%d%s", parsed_url.scheme, parsed_url.host, parsed_url.port, parsed_url.path)

  -- note: `httpc:request` makes a deep copy of `params_cache`, so it will be
  -- fine to reuse the table here
  local res, err = httpc:request_uri(url, params_cache)
  if not res then
    return nil, "failed request to " .. host .. ":" .. tostring(port) .. ": " .. err
  end

  -- always read response body, even if we discard it without using it on success
  local response_body = res.body
  local success = res.status < 400
  local err_msg

  if not success then
    err_msg = "request to " .. host .. ":" .. tostring(port) ..
      " returned status code " .. tostring(res.status) .. " and body " ..
      response_body
  end

  return success, err_msg
end


function SelfPermissionHandler:init_worker()

  -- your custom code here
  kong.log.debug("==================saying hi from the 'init_worker' handler")

end --]]

function SelfPermissionHandler:access(plugin_conf)
  -- 1. 获取用户的请求头的key
  local customerKey = kong.request.get_header("x-custom-key")
  if not customerKey then
    kong.log.err("====customer key is null, please check request")
    return kong.response.error(402, "Please config a customer key for this request")
  end
  kong.log("====customer key is :", customerKey)

  --2. 校验key 是否符合规则
  local ok, err = check_customerKey(customerKey)
  if err then
    kong.log.err("====customer key is invalid, please check:", customerKey)
  end

  --3. 请求权限中心,获取该key的权限
  local path = kong.request.get_path()
  kong.log("====path is:", path)
  local config_header_key = plugin_conf.header_key
  kong.log("====config_header_key is:", config_header_key)
  if config_header_key ~= customerKey then
    return kong.response.error(403, "The key is invalid, please check")
  end
  local body ={
    customerKey,
    path,
  }
  body.customerKey=customerKey
  body.path=path
  local bodyStr = cjson.encode(body)
  kong.log("====body is :", bodyStr)
  local success, err_msg = send_payload(self, plugin_conf, bodyStr)
  if err_msg then
    kong.log.err("====Permission server error:", err_msg)
    return kong.response.error(440, "Sorry, you don't have this resource permission, please check")
  end
  
  kong.log.inspect(plugin_conf)

end


-- return our plugin object
return SelfPermissionHandler

复制代码
  1. schema.lua
local typedefs = require "kong.db.schema.typedefs"
local url = require "socket.url"

return {
  name = "self-permission",
  fields = {
    { protocols = typedefs.protocols },
    { config = {
      type = "record",
      fields = {
        -- NOTE: any field added here must be also included in the handler's get_queue_id method
        { http_endpoint = typedefs.url({ required = true, encrypted = true }) }, -- encrypted = true is a Kong-Enterprise exclusive feature, does nothing in Kong CE
        { method = { type = "string", default = "POST", one_of = { "POST", "PUT", "PATCH" }, }, },
        { content_type = { type = "string", default = "application/json", one_of = { "application/json" }, }, },
        { header_key = { type = "string", default = "", required=true },},
        { timeout = { type = "number", default = 10000 }, },
        { keepalive = { type = "number", default = 60000 }, },
        { retry_count = { type = "integer", default = 10 }, },
        { queue_size = { type = "integer", default = 1 }, },
        { flush_timeout = { type = "number", default = 2 }, },
        { headers = typedefs.headers {
          keys = typedefs.header_name {
            match_none = {
              {
                pattern = "^[Hh][Oo][Ss][Tt]$",
                err = "cannot contain 'Host' header",
              },
              {
                pattern = "^[Cc][Oo][Nn][Tt][Ee][Nn][Tt]%-[Ll][Ee][nn][Gg][Tt][Hh]$",
                err = "cannot contain 'Content-Length' header",
              },
              {
                pattern = "^[Cc][Oo][Nn][Tt][Ee][Nn][Tt]%-[Tt][Yy][Pp][Ee]$",
                err = "cannot contain 'Content-Type' header",
              },
            },
          },
        } },
      },
      custom_validator = function(config)
        -- check no double userinfo + authorization header
        local parsed_url = url.parse(config.http_endpoint)
        if parsed_url.userinfo and config.headers then
          for hname, hvalue in pairs(config.headers) do
            if hname:lower() == "authorization" then
              return false, "specifying both an 'Authorization' header and user info in 'http_endpoint' is not allowed"
            end
          end
        end
        return true
      end,
    },
    },
  },
}
复制代码

要約:

プラグインリファレンス:http-log、2つのネイティブプラグインのレート制限ソースコード、記述、使用されるライブラリは次のとおりです。

  1. ローカルhttpUtil=「resty.http」が必要
  2. ローカルtable_clear=「table.clear」が必要
  3. ローカルURL=「socket.url」が必要
  4. ローカルcjson=「cjson」が必要

おすすめ

転載: juejin.im/post/7101561475621191716