задний план
Версия шлюза обновлена (с 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
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 theAuthorization
header.\
Получите JWT из запроса, а логика получения последовательно из параметров URL, файлов cookie и последнего заголовка запроса.
-
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)
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")
)
;
Verify "alg"
Verify the JWT signature
Verify the JWT registered claims
Retrieve the consumer
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
разница:
- Версия 0.12.0 jwt
do_authentication
изjwt_secret_key
Получено только из полезной нагрузки jwt и не получено дополнительно из заголовка запроса;
local claims = jwt.claims
local jwt_secret_key = claims[conf.key_claim_name]
- 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
- 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 используется для проверки времени истечения срока действия токена, в результате чего токен с истекшим сроком действия отображается в обновленном шлюзе, но не отображается в старом шлюзе.