Детали реализации плагина jwt для шлюза Kong

Lua

задний план

Версия шлюза обновлена ​​(с 0.12.0 до 0.14.1).В процессе обновления тот же запрос на новую версию шлюза сообщает 401. Из access.log видно, что плагин успешно разобрал токен и успешно получил соответствующий Token.Consumer. Поэтому необходимо сравнить логические различия между двумя версиями плагина.

Обновление Конга

Прежде чем рассматривать обновление jwt, давайте взглянем на модификацию kong с 0.12.0 до 0.14.1. Kong 0.14.1 намеревается убить концепцию API, а затем вводит концепцию Service plus Router.

Kong с API в качестве измерения

Конг концепции «Сервис+маршрутизатор»

По сравнению с 0.12.0 и предыдущими версиями последняя версия Kong поддерживает подключаемый модуль для управления группой служб для достижения эффекта повторного использования подключаемого модуля. Степень детализации управления услугами также выше.

jwt-плагин

0.14.1

local function do_authentication(conf)
  local token, err = retrieve_token(ngx.req, conf)
  if err then
    return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
  end

  local ttype = type(token)
  if ttype ~= "string" then
    if ttype == "nil" then
      return false, {status = 401}
    elseif ttype == "table" then
      return false, {status = 401, message = "Multiple tokens provided"}
    else
      return false, {status = 401, message = "Unrecognizable token"}
    end
  end

  -- Decode token to find out who the consumer is
  local jwt, err = jwt_decoder:new(token)
  if err then
    return false, {status = 401, message = "Bad token; " .. tostring(err)}
  end

  local claims = jwt.claims
  local header = jwt.header

  local jwt_secret_key = claims[conf.key_claim_name] or header[conf.key_claim_name]
  if not jwt_secret_key then
    return false, {status = 401, message = "No mandatory '" .. conf.key_claim_name .. "' in claims"}
  end

  -- Retrieve the secret
  local jwt_secret_cache_key = singletons.dao.jwt_secrets:cache_key(jwt_secret_key)
  local jwt_secret, err      = singletons.cache:get(jwt_secret_cache_key, nil,
                                                    load_credential, jwt_secret_key)
  if err then
    return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
  end

  if not jwt_secret then
    return false, {status = 403, message = "No credentials found for given '" .. conf.key_claim_name .. "'"}
  end

  local algorithm = jwt_secret.algorithm or "HS256"

  -- Verify "alg"
  if jwt.header.alg ~= algorithm then
    return false, {status = 403, message = "Invalid algorithm"}
  end

  local jwt_secret_value = algorithm ~= nil and algorithm:sub(1, 2) == "HS" and
                             jwt_secret.secret or jwt_secret.rsa_public_key
  if conf.secret_is_base64 then
    jwt_secret_value = jwt:b64_decode(jwt_secret_value)
  end

  if not jwt_secret_value then
    return false, {status = 403, message = "Invalid key/secret"}
  end

  -- Now verify the JWT signature
  if not jwt:verify_signature(jwt_secret_value) then
    return false, {status = 403, message = "Invalid signature"}
  end

  -- Verify the JWT registered claims
  local ok_claims, errors = jwt:verify_registered_claims(conf.claims_to_verify)
  if not ok_claims then
    return false, {status = 401, message = errors}
  end

  -- Verify the JWT registered claims
  if conf.maximum_expiration ~= nil and conf.maximum_expiration > 0 then
    local ok, errors = jwt:check_maximum_expiration(conf.maximum_expiration)
    if not ok then
      return false, {status = 403, message = errors}
    end
  end

  -- Retrieve the consumer
  local consumer_cache_key = singletons.db.consumers:cache_key(jwt_secret.consumer_id)
  local consumer, err      = singletons.cache:get(consumer_cache_key, nil,
                                                  load_consumer,
                                                  jwt_secret.consumer_id, true)
  if err then
    return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
  end

  -- However this should not happen
  if not consumer then
    return false, {status = 403, message = string_format("Could not find consumer for '%s=%s'", conf.key_claim_name, jwt_secret_key)}
  end

  set_consumer(consumer, jwt_secret, token)

  return true
end
  1. local token, err = retrieve_token(ngx.req, conf)

-- Retrieve a JWT in a request.
-- Checks for the JWT in URI parameters, then in cookies, and finally
-- in the Authorization header.\

Получите JWT из запроса, а логика получения последовательно из параметров URL, файлов cookie и последнего заголовка запроса.

  1. local jwt, err = jwt_decoder:new(token)Декодируйте токен, чтобы получить объект jwt. Вот введение в структуру jwt: jwt в основном состоит из трех частей:
  • заголовок: Объясняет предыдущий алгоритм jwt. Как показаноHS256;
  • полезная нагрузка: тело сообщения содержит содержимое токена;
  • подпись: кодирование Base64 выполняется для заголовка и полезной нагрузки соответственно, а перекодированные заголовок и полезная нагрузка сращиваются в соответствии с разделителем. Подпись получается секретным вычислением.
key = 'secret'  
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)  
signature = HMAC-SHA256(key, unsignedToken) 

  1. Retrieve the secret
  local claims = jwt.claims
  local header = jwt.header

  local jwt_secret_key = claims[conf.key_claim_name] or header[conf.key_claim_name]
  if not jwt_secret_key then
    return false, {status = 401, message = "No mandatory '" .. conf.key_claim_name .. "' in claims"}
  end

  local jwt_secret_cache_key = singletons.dao.jwt_secrets:cache_key(jwt_secret_key)
  local jwt_secret, err      = singletons.cache:get(jwt_secret_cache_key, nil,
                                                    load_credential, jwt_secret_key)

Получите секрет.conf.key_claim_nameэто имя ключа, настроенного плагиномiss. получатьissПосле соответствующего секрета найти соответствующий секрет из кешаjwt_secretЗаписывать.jwt_secretЭто таблица внутри Kong, в которой хранится соответствие между потребителем и секретом. Сторона доступа анализирует запрошенный маркер. Здесь можно сделать многое, но плагин jwt просто проверяет легитимность токена.

CREATE TABLE "public"."jwt_secrets" (
  "id" uuid NOT NULL,
  "consumer_id" uuid,
  "key" text COLLATE "pg_catalog"."default",
  "secret" text COLLATE "pg_catalog"."default",
  "created_at" timestamp(6) DEFAULT timezone('utc'::text, ('now'::text)::timestamp(0) with time zone),
  "algorithm" text COLLATE "pg_catalog"."default",
  "rsa_public_key" text COLLATE "pg_catalog"."default",
  CONSTRAINT "jwt_secrets_pkey" PRIMARY KEY ("id"),
  CONSTRAINT "jwt_secrets_consumer_id_fkey" FOREIGN KEY ("consumer_id") REFERENCES "public"."consumers" ("id") ON DELETE CASCADE ON UPDATE NO ACTION,
  CONSTRAINT "jwt_secrets_key_key" UNIQUE ("key")
)
;
  1. Verify "alg"
  2. Verify the JWT signature
  3. Verify the JWT registered claims
  4. Retrieve the consumer
  5. set_consumer

0.12.0

local function do_authentication(conf)
  local token, err = retrieve_token(ngx.req, conf)
  if err then
    return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
  end

  local ttype = type(token)
  if ttype ~= "string" then
    if ttype == "nil" then
      return false, {status = 401}
    elseif ttype == "table" then
      return false, {status = 401, message = "Multiple tokens provided"}
    else
      return false, {status = 401, message = "Unrecognizable token"}
    end
  end

  -- Decode token to find out who the consumer is
  local jwt, err = jwt_decoder:new(token)
  if err then
    return false, {status = 401, message = "Bad token; " .. tostring(err)}
  end

  local claims = jwt.claims

  local jwt_secret_key = claims[conf.key_claim_name]
  if not jwt_secret_key then
    return false, {status = 401, message = "No mandatory '" .. conf.key_claim_name .. "' in claims"}
  end

  -- Retrieve the secret
  local jwt_secret_cache_key = singletons.dao.jwt_secrets:cache_key(jwt_secret_key)
  local jwt_secret, err      = singletons.cache:get(jwt_secret_cache_key, nil,
                                                    load_credential, jwt_secret_key)
  if err then
    return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
  end

  if not jwt_secret then
    return false, {status = 403, message = "No credentials found for given '" .. conf.key_claim_name .. "'"}
  end

  local algorithm = jwt_secret.algorithm or "HS256"

  -- Verify "alg"
  if jwt.header.alg ~= algorithm then
    return false, {status = 403, message = "Invalid algorithm"}
  end

  local jwt_secret_value = algorithm == "HS256" and jwt_secret.secret or jwt_secret.rsa_public_key
  if conf.secret_is_base64 then
    jwt_secret_value = jwt:b64_decode(jwt_secret_value)
  end

  if not jwt_secret_value then
    return false, {status = 403, message = "Invalid key/secret"}
  end

  -- Now verify the JWT signature
  if not jwt:verify_signature(jwt_secret_value) then
    return false, {status = 403, message = "Invalid signature"}
  end

  -- Verify the JWT registered claims
  local ok_claims, errors = jwt:verify_registered_claims(conf.claims_to_verify)
  if not ok_claims then
    return false, {status = 401, message = errors}
  end

  -- Retrieve the consumer
  local consumer_cache_key = singletons.dao.consumers:cache_key(jwt_secret.consumer_id)
  local consumer, err      = singletons.cache:get(consumer_cache_key, nil,
                                                  load_consumer,
                                                  jwt_secret.consumer_id, true)
  if err then
    return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
  end

  -- However this should not happen
  if not consumer then
    return false, {status = 403, message = string_format("Could not find consumer for '%s=%s'", conf.key_claim_name, jwt_secret_key)}
  end

  set_consumer(consumer, jwt_secret)

  return true
end

разница:

  1. Версия 0.12.0 jwtdo_authenticationизjwt_secret_keyПолучено только из полезной нагрузки jwt и не получено дополнительно из заголовка запроса;
local claims = jwt.claims

local jwt_secret_key = claims[conf.key_claim_name]
  1. 0.14.1 Поддержка расширения алгоритма HS
  local jwt_secret_value = algorithm == "HS256" and jwt_secret.secret or jwt_secret.rsa_public_key
  if conf.secret_is_base64 then
    jwt_secret_value = jwt:b64_decode(jwt_secret_value)
  end
  1. 0.12.1 не проверяет действительность токена
  -- Verify the JWT registered claims
  if conf.maximum_expiration ~= nil and conf.maximum_expiration > 0 then
    local ok, errors = jwt:check_maximum_expiration(conf.maximum_expiration)
    if not ok then
      return false, {status = 403, message = errors}
    end
  end

Объединив сравнение, можно в основном определить, что 0.14.1 используется для проверки времени истечения срока действия токена, в результате чего токен с истекшим сроком действия отображается в обновленном шлюзе, но не отображается в старом шлюзе.