jwt: JWT для великого nginx-module-lua
Установка
Если вы еще не настроили подписку на репозиторий RPM, зарегистрируйтесь. Затем вы можете продолжить с следующими шагами.
CentOS/RHEL 7 или Amazon Linux 2
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-jwt
CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-jwt
Чтобы использовать эту библиотеку Lua с NGINX, убедитесь, что nginx-module-lua установлен.
Этот документ описывает lua-resty-jwt v0.1.11, выпущенную 11 июля 2017 года.
Эта библиотека требует сборку nginx с OpenSSL, ngx_lua module, LuaJIT 2.0, lua-resty-hmac и lua-resty-string.
Синопсис
# nginx.conf:
server {
default_type text/plain;
location = /verify {
content_by_lua '
local cjson = require "cjson"
local jwt = require "resty.jwt"
local jwt_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" ..
".eyJmb28iOiJiYXIifQ" ..
".VAoRL1IU0nOguxURF2ZcKR0SGKE1gCbqwyh8u2MLAyY"
local jwt_obj = jwt:verify("lua-resty-jwt", jwt_token)
ngx.say(cjson.encode(jwt_obj))
';
}
location = /sign {
content_by_lua '
local cjson = require "cjson"
local jwt = require "resty.jwt"
local jwt_token = jwt:sign(
"lua-resty-jwt",
{
header={typ="JWT", alg="HS256"},
payload={foo="bar"}
}
)
ngx.say(jwt_token)
';
}
}
Методы
Чтобы загрузить эту библиотеку,
- вам нужно указать путь к этой библиотеке в директиве ngx_lua lua_package_path. Например,
lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";. - вы используете
require, чтобы загрузить библиотеку в локальную переменную Lua:
local jwt = require "resty.jwt"
sign
syntax: local jwt_token = jwt:sign(key, table_of_jwt)
подписывает table_of_jwt в jwt_token.
Аргумент alg указывает, какой алгоритм хеширования использовать (HS256, HS512, RS256).
пример table_of_jwt
{
"header": {"typ": "JWT", "alg": "HS512"},
"payload": {"foo": "bar"}
}
verify
syntax: local jwt_obj = jwt:verify(key, jwt_token [, claim_spec [, ...]])
проверяет jwt_token и возвращает таблицу jwt_obj. key может быть заранее определенным ключом (в виде строки), или функцией, которая принимает один параметр (значение kid из заголовка) и возвращает либо заранее определенный ключ (в виде строки) для kid, либо nil, если поиск kid не удался. Этот вызов завершится неудачей, если вы попытаетесь указать функцию для key, и в заголовке не будет существовать kid.
Смотрите Verification для получения деталей о формате параметров claim_spec.
load & verify
syntax: local jwt_obj = jwt:load_jwt(jwt_token)
syntax: local verified = jwt:verify_jwt_obj(key, jwt_obj [, claim_spec [, ...]])
verify = load_jwt + verify_jwt_obj
загружает jwt, проверяет kid, затем проверяет его с правильным ключом.
пример jwt_obj
{
"raw_header": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
"raw_payload": "eyJmb28iOiJiYXIifQ",
"signature": "wrong-signature",
"header": {"typ": "JWT", "alg": "HS256"},
"payload": {"foo": "bar"},
"verified": false,
"valid": true,
"reason": "signature mismatched: wrong-signature"
}
sign-jwe
syntax: local jwt_token = jwt:sign(key, table_of_jwt)
подписывает table_of_jwt в jwt_token.
Аргумент alg указывает, какой алгоритм хеширования использовать для шифрования ключа (dir).
Аргумент enc указывает, какой алгоритм хеширования использовать для шифрования полезной нагрузки (A128CBC-HS256, A256CBC-HS512).
пример table_of_jwt
{
"header": {"typ": "JWE", "alg": "dir", "enc":"A128CBC-HS256"},
"payload": {"foo": "bar"}
}
Проверка
Обе функции jwt:load и jwt:verify_jwt_obj принимают в качестве дополнительных параметров любое количество необязательных параметров claim_spec. claim_spec — это просто таблица lua с утверждениями и валидаторами. Каждый ключ в таблице claim_spec соответствует соответствующему ключу в полезной нагрузке, а validator — это функция, которая будет вызвана, чтобы определить, выполнены ли утверждения.
Подпись функции validator выглядит следующим образом:
function(val, claim, jwt_json)
Где val — это значение утверждения из проверяемого jwt_obj (или nil, если оно не существует в полезной нагрузке объекта), claim — это имя утверждения, которое проверяется, а jwt_json — это сериализованное в json представление объекта, который проверяется. Если функции не нужны параметры claim или jwt_json, их можно опустить.
Функция validator возвращает либо true, либо false. Любой validator МОЖЕТ вызвать ошибку, и проверка будет считаться неудачной, а ошибка, которая была вызвана, будет помещена в поле причины результирующего объекта. Если validator ничего не возвращает (т.е. nil), то функция считается успешной — при условии, что она вызвала бы ошибку, если бы не удалась.
Специальное утверждение с именем __jwt может быть использовано так, что если для него существует функция validator, то validator будет вызвана с глубоким клоном всего разобранного jwt объекта в качестве значения val. Это позволяет вам писать проверки для всего объекта, которые могут зависеть от одного или нескольких утверждений.
Несколько таблиц claim_spec могут быть указаны для jwt:load и jwt:verify_jwt_obj — и они будут выполняться по порядку. Нет гарантии порядка выполнения отдельных validators внутри одной claim_spec. Если claim_spec не удается, то любые последующие claim_specs НЕ будут выполнены.
пример claim_spec
{
sub = function(val) return string.match("^[a-z]+$", val) end,
iss = function(val)
for _, value in pairs({ "first", "second" }) do
if value == val then return true end
end
return false
end,
__jwt = function(val, claim, jwt_json)
if val.payload.foo == nil or val.payload.bar == nil then
error("Need to specify either 'foo' or 'bar'")
end
end
}
Валидаторы JWT
Существует библиотека полезных функций validator в resty.jwt-validators. Вы можете использовать эту библиотеку, включив:
local validators = require "resty.jwt-validators"
В настоящее время в библиотеке валидаторов определены следующие функции. Те, которые помечены "(opt)", означают, что существует такая же функция с именем opt_<name>, которая принимает те же параметры. "opt" версия функции вернет true, если ключ не существует в полезной нагрузке проверяемого jwt_object, в то время как "non-opt" версия функции вернет false, если ключ не существует в полезной нагрузке проверяемого jwt_object.
validators.chain(...)
Возвращает валидатор, который объединяет данные функции вместе, одну за другой - пока они продолжают проходить свои проверки.
validators.required(chain_function)
Возвращает валидатор, который возвращает false, если значение не существует. Если значение существует и указана chain_function, то будет возвращено значение chain_function(val, claim, jwt_json), в противном случае будет возвращено true. Это позволяет указать, что значение является обязательным и должно соответствовать некоторой дополнительной проверке.
validators.require_one_of(claim_keys)
Возвращает валидатор, который выдает ошибку с сообщением, если НИ ОДНО из указанных ключей утверждений не существует. Ожидается, что эта функция будет использоваться для полного jwt объекта. claim_keys должны быть непустой таблицей строк.
validators.check(check_val, check_function, name, check_type) (opt)
Возвращает валидатор, который проверяет, возвращает ли результат вызова данной check_function для проверяемого значения и check_val true. Значение check_val и check_function не могут быть nil. Необязательное name используется для сообщений об ошибках и по умолчанию равно "check_value". Необязательный check_type используется для того, чтобы убедиться, что тип проверки совпадает и по умолчанию равен type(check_val). Первый параметр, переданный в check_function, никогда не будет nil. Если check_function вызывает ошибку, она будет добавлена к сообщению об ошибке.
validators.equals(check_val) (opt)
Возвращает валидатор, который проверяет, равно ли значение точно (с использованием ==) данному check_value. Значение check_val не может быть nil.
validators.matches(pattern) (opt)
Возвращает валидатор, который проверяет, соответствует ли значение данному шаблону (с использованием string.match). Значение pattern должно быть строкой.
validators.any_of(check_values, check_function, name, check_type, table_type) (opt)
Возвращает валидатор, который вызывает данную check_function для каждого из указанных check_values и проверяемого значения. Если любой из этих вызовов возвращает true, то эта функция возвращает true. Значение check_values должно быть непустой таблицей со всеми одинаковыми типами, а значение check_function не должно быть nil. Необязательное name используется для сообщений об ошибках и по умолчанию равно "check_values". Необязательный check_type используется для того, чтобы убедиться, что тип проверки совпадает и по умолчанию равен type(check_values[1]) - тип таблицы.
validators.equals_any_of(check_values) (opt)
Возвращает валидатор, который проверяет, равно ли значение точно любому из указанных check_values.
validators.matches_any_of(patterns) (opt)
Возвращает валидатор, который проверяет, соответствует ли значение любому из указанных patterns.
validators.contains_any_of(check_values,name) (opt)
Возвращает валидатор, который проверяет, существует ли значение ожидаемого типа string в любом из указанных check_values. Значение check_values должно быть непустой таблицей со всеми одинаковыми типами. Необязательное name используется для сообщений об ошибках и по умолчанию равно check_values.
validators.greater_than(check_val) (opt)
Возвращает валидатор, который проверяет, как значение сравнивается (численно, с использованием >) с данным check_value. Значение check_val не может быть nil и должно быть числом.
validators.greater_than_or_equal(check_val) (opt)
Возвращает валидатор, который проверяет, как значение сравнивается (численно, с использованием >=) с данным check_value. Значение check_val не может быть nil и должно быть числом.
validators.less_than(check_val) (opt)
Возвращает валидатор, который проверяет, как значение сравнивается (численно, с использованием <) с данным check_value. Значение check_val не может быть nil и должно быть числом.
validators.less_than_or_equal(check_val) (opt)
Возвращает валидатор, который проверяет, как значение сравнивается (численно, с использованием <=) с данным check_value. Значение check_val не может быть nil и должно быть числом.
validators.is_not_before() (opt)
Возвращает валидатор, который проверяет, что текущее время не раньше проверяемого значения в пределах допустимого отклонения системы. Это означает, что:
val <= (system_clock() + system_leeway).
validators.is_not_expired() (opt)
Возвращает валидатор, который проверяет, что текущее время не равно или не позже проверяемого значения в пределах допустимого отклонения системы. Это означает, что:
val > (system_clock() - system_leeway).
validators.is_at() (opt)
Возвращает валидатор, который проверяет, что текущее время совпадает с проверяемым значением в пределах допустимого отклонения системы. Это означает, что:
val >= (system_clock() - system_leeway) and val <= (system_clock() + system_leeway).
validators.set_system_leeway(leeway)
Функция для установки допустимого отклонения (в секундах), используемого для is_not_before и is_not_expired. По умолчанию используется 0 секунд.
validators.set_system_clock(clock)
Функция для установки системных часов, используемых для is_not_before и is_not_expired. По умолчанию используется ngx.now.
пример claim_spec, использующего валидаторы
local validators = require "resty.jwt-validators"
local claim_spec = {
sub = validators.opt_matches("^[a-z]+$"),
iss = validators.equals_any_of({ "first", "second" }),
__jwt = validators.require_one_of({ "foo", "bar" })
}
Параметры устаревшей версии/временные рамки
Чтобы поддерживать код, который использовал предыдущие версии этой библиотеки, а также для упрощения указания claim_specs, основанных на временных рамках, вы можете использовать вместо любого одного параметра claim_spec таблицу validation_options. Параметр должен быть представлен в виде таблицы ключ/значение. Каждый ключ таблицы должен быть выбран из следующего списка.
При использовании устаревших validation_options вы ДОЛЖНЫ УКАЖИТЬ ТОЛЬКО эти параметры. То есть, вы не можете смешивать устаревшие validation_options с другими валидаторами claim_spec. Чтобы достичь этого, вы должны указать несколько параметров для функций jwt:load/jwt:verify_jwt_obj.
-
lifetime_grace_period: Определите допустимое отклонение в секундах для учета расхождения часов между сервером, который сгенерировал jwt, и сервером, который его проверяет. Значение должно быть нулевым (0) или положительным целым числом.-
Когда этот параметр проверки указан, процесс будет гарантировать, что jwt содержит хотя бы одно из двух утверждений
nbfилиexpи сравнивать текущее время с этими границами. Если jwt будет признан истекшим или недействительным, проверка завершится неудачей. -
Когда ни одно из утверждений
nbfиexpне может быть найдено, проверка завершится неудачей. -
Ожидается, что утверждения
nbfиexpбудут выражены в jwt в виде числовых значений. Если это не так, проверка завершится неудачей. -
Указание этого параметра эквивалентно вызову:
validators.set_system_leeway(leeway)
и указанию в качестве
claim_spec:{ __jwt = validators.require_one_of({ "nbf", "exp" }), nbf = validators.opt_is_not_before(), exp = validators.opt_is_not_expired() } -
-
require_nbf_claim: Укажите, является ли утверждениеnbfнеобязательным или нет. Значение должно быть булевым.-
Когда этот параметр проверки установлен в
trueи не указанlifetime_grace_period, подразумевается нулевое (0) отклонение. -
Указание этого параметра эквивалентно указанию в качестве
claim_spec:{ nbf = validators.is_not_before(), }
-
-
require_exp_claim: Укажите, является ли утверждениеexpнеобязательным или нет. Значение должно быть булевым.-
Когда этот параметр проверки установлен в
trueи не указанlifetime_grace_period, подразумевается нулевое (0) отклонение. -
Указание этого параметра эквивалентно указанию в качестве
claim_spec:{ exp = validators.is_not_expired(), }
-
-
valid_issuers: Белый список проверенных эмитентов jwt. Значение должно быть массивом строк.-
Когда этот параметр проверки указан, процесс будет сравнивать утверждение jwt
issсо списком действительных эмитентов. Сравнение выполняется с учетом регистра. Если эмитент jwt не найден в белом списке, проверка завершится неудачей. -
Ожидается, что утверждение
issбудет выражено в jwt в виде строки. Если это не так, проверка завершится неудачей. -
Указание этого параметра эквивалентно указанию в качестве
claim_spec:{ iss = validators.equals_any_of(valid_issuers), }
-
пример использования validation_options
local jwt_obj = jwt:verify(key, jwt_token,
{
lifetime_grace_period = 120,
require_exp_claim = true,
valid_issuers = { "my-trusted-issuer", "my-other-trusteed-issuer" }
}
)
Примеры
Тестирование с Docker
./ci script
См. также
- модуль ngx_lua: http://wiki.nginx.org/HttpLuaModule
GitHub
Вы можете найти дополнительные советы по конфигурации и документацию для этого модуля в репозитории GitHub для nginx-module-jwt.