jwt: JWT Para O Grande nginx-module-lua
Instalação
Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Em seguida, você pode prosseguir com os seguintes passos.
CentOS/RHEL 7 ou 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
Para usar esta biblioteca Lua com NGINX, certifique-se de que o nginx-module-lua está instalado.
Este documento descreve lua-resty-jwt v0.1.11 lançado em 11 de julho de 2017.
Esta biblioteca requer uma compilação do nginx com OpenSSL, o módulo ngx_lua, o LuaJIT 2.0, o lua-resty-hmac e o lua-resty-string.
Sinopse
# 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)
';
}
}
Métodos
Para carregar esta biblioteca,
- você precisa especificar o caminho desta biblioteca na diretiva lua_package_path do ngx_lua. Por exemplo,
lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";. - você usa
requirepara carregar a biblioteca em uma variável Lua local:
local jwt = require "resty.jwt"
sign
syntax: local jwt_token = jwt:sign(key, table_of_jwt)
assina uma table_of_jwt para um jwt_token.
O argumento alg especifica qual algoritmo de hash usar (HS256, HS512, RS256).
exemplo de table_of_jwt
{
"header": {"typ": "JWT", "alg": "HS512"},
"payload": {"foo": "bar"}
}
verify
syntax: local jwt_obj = jwt:verify(key, jwt_token [, claim_spec [, ...]])
verifica um jwt_token e retorna uma tabela jwt_obj. key pode ser uma chave pré-compartilhada (como uma string), ou uma função que recebe um único parâmetro (o valor de kid do cabeçalho) e retorna a chave pré-compartilhada (como uma string) para o kid ou nil se a busca pelo kid falhar. Esta chamada falhará se você tentar especificar uma função para key e não houver um kid existente no cabeçalho.
Veja Verificação para detalhes sobre o formato dos parâmetros 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
carrega jwt, verifica o kid e, em seguida, verifica-o com a chave correta.
exemplo de 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)
assina uma table_of_jwt para um jwt_token.
O argumento alg especifica qual algoritmo de hash usar para criptografar a chave (dir). O argumento enc especifica qual algoritmo de hash usar para criptografar o payload (A128CBC-HS256, A256CBC-HS512).
exemplo de table_of_jwt
{
"header": {"typ": "JWE", "alg": "dir", "enc":"A128CBC-HS256"},
"payload": {"foo": "bar"}
}
Verificação
Tanto as funções jwt:load quanto jwt:verify_jwt_obj aceitam, como parâmetros adicionais, qualquer número de parâmetros opcionais claim_spec. Um claim_spec é simplesmente uma tabela lua de reivindicações e validadores. Cada chave na tabela claim_spec corresponde a uma chave correspondente no payload, e o validator é uma função que será chamada para determinar se as reivindicações são atendidas.
A assinatura de uma função validator é:
function(val, claim, jwt_json)
Onde val é o valor da reivindicação do jwt_obj sendo testado (ou nil se não existir no payload do objeto), claim é o nome da reivindicação que está sendo verificada, e jwt_json é uma representação serializada em json do objeto que está sendo verificado. Se a função não precisar dos parâmetros claim ou jwt_json, eles podem ser omitidos.
Uma função validator retorna true ou false. Qualquer validator PODE gerar um erro, e a validação será tratada como uma falha, e o erro gerado será colocado no campo de razão do objeto resultante. Se um validator não retornar nada (ou seja, nil), então a função é tratada como tendo sido bem-sucedida - sob a suposição de que teria gerado um erro se tivesse falhado.
Uma reivindicação especial chamada __jwt pode ser usada de forma que, se uma função validator existir para ela, então o validator será chamado com uma cópia profunda de todo o objeto jwt analisado como o valor de val. Isso é para que você possa escrever verificações para um objeto inteiro que pode depender de uma ou mais reivindicações.
Múltiplas tabelas claim_spec podem ser especificadas para jwt:load e jwt:verify_jwt_obj - e elas serão executadas em ordem. Não há garantia da ordem de execução dos validators individuais dentro de um único claim_spec. Se um claim_spec falhar, então quaisquer claim_specs seguintes NÃO serão executados.
exemplo de 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
}
Validadores JWT
Uma biblioteca de funções validator úteis existe em resty.jwt-validators. Você pode usar esta biblioteca incluindo:
local validators = require "resty.jwt-validators"
As seguintes funções estão atualmente definidas na biblioteca de validadores. Aqueles marcados com "(opt)" significam que a mesma função existe chamada opt_<name> que aceita os mesmos parâmetros. A versão "opt" da função retornará true se a chave não existir no payload do jwt_object sendo verificado, enquanto a versão "não opt" da função retornará false se a chave não existir no payload do jwt_object sendo verificado.
validators.chain(...)
Retorna um validador que encadeia as funções dadas, uma após a outra - enquanto elas continuarem passando suas verificações.
validators.required(chain_function)
Retorna um validador que retorna false se um valor não existir. Se o valor existir e uma chain_function for especificada, então o valor de chain_function(val, claim, jwt_json) será retornado, caso contrário, true será retornado. Isso permite especificar que um valor é tanto obrigatório quanto deve corresponder a alguma verificação adicional.
validators.require_one_of(claim_keys)
Retorna um validador que gera um erro com uma mensagem se NENHUMA das chaves de reivindicação dadas existir. Espera-se que esta função seja usada contra um objeto jwt completo. As claim_keys devem ser uma tabela não vazia de strings.
validators.check(check_val, check_function, name, check_type) (opt)
Retorna um validador que verifica se o resultado da chamada da função check_function para o valor testado e check_val retorna verdadeiro. O valor de check_val e check_function não podem ser nil. O nome opcional é usado para mensagens de erro e tem como padrão "check_value". O tipo de verificação opcional é usado para garantir que o tipo de verificação corresponda e tem como padrão type(check_val). O primeiro parâmetro passado para check_function nunca será nil. Se a check_function gerar um erro, isso será anexado à mensagem de erro.
validators.equals(check_val) (opt)
Retorna um validador que verifica se um valor é exatamente igual (usando ==) ao valor de verificação dado. O valor de check_val não pode ser nil.
validators.matches(pattern) (opt)
Retorna um validador que verifica se um valor corresponde ao padrão dado (usando string.match). O valor de pattern deve ser uma string.
validators.any_of(check_values, check_function, name, check_type, table_type) (opt)
Retorna um validador que chama a função check_function dada para cada um dos check_values dados e o valor testado. Se alguma dessas chamadas retornar true, então esta função retorna true. O valor de check_values deve ser uma tabela não vazia com todos os mesmos tipos, e o valor de check_function não deve ser nil. O nome opcional é usado para mensagens de erro e tem como padrão "check_values". O tipo de verificação opcional é usado para garantir que o tipo de verificação corresponda e tem como padrão type(check_values[1]) - o tipo da tabela.
validators.equals_any_of(check_values) (opt)
Retorna um validador que verifica se um valor é exatamente igual a qualquer um dos check_values dados.
validators.matches_any_of(patterns) (opt)
Retorna um validador que verifica se um valor corresponde a qualquer um dos patterns dados.
validators.contains_any_of(check_values,name) (opt)
Retorna um validador que verifica se um valor do tipo esperado string existe em qualquer um dos check_values dados. O valor de check_values deve ser uma tabela não vazia com todos os mesmos tipos. O nome opcional é usado para mensagens de erro e tem como padrão check_values.
validators.greater_than(check_val) (opt)
Retorna um validador que verifica como um valor se compara (numericamente, usando >) a um dado check_value. O valor de check_val não pode ser nil e deve ser um número.
validators.greater_than_or_equal(check_val) (opt)
Retorna um validador que verifica como um valor se compara (numericamente, usando >=) a um dado check_value. O valor de check_val não pode ser nil e deve ser um número.
validators.less_than(check_val) (opt)
Retorna um validador que verifica como um valor se compara (numericamente, usando <) a um dado check_value. O valor de check_val não pode ser nil e deve ser um número.
validators.less_than_or_equal(check_val) (opt)
Retorna um validador que verifica como um valor se compara (numericamente, usando <=) a um dado check_value. O valor de check_val não pode ser nil e deve ser um número.
validators.is_not_before() (opt)
Retorna um validador que verifica se o tempo atual não é anterior ao valor testado dentro da margem de manobra do sistema. Isso significa que:
val <= (system_clock() + system_leeway).
validators.is_not_expired() (opt)
Retorna um validador que verifica se o tempo atual não é igual ou posterior ao valor testado dentro da margem de manobra do sistema. Isso significa que:
val > (system_clock() - system_leeway).
validators.is_at() (opt)
Retorna um validador que verifica se o tempo atual é o mesmo que o valor testado dentro da margem de manobra do sistema. Isso significa que:
val >= (system_clock() - system_leeway) and val <= (system_clock() + system_leeway).
validators.set_system_leeway(leeway)
Uma função para definir a margem de manobra (em segundos) usada para is_not_before e is_not_expired. O padrão é usar 0 segundos.
validators.set_system_clock(clock)
Uma função para definir o relógio do sistema usado para is_not_before e is_not_expired. O padrão é usar ngx.now.
exemplo de claim_spec usando validadores
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" })
}
Opções Legadas/De Período
Para suportar código que usou versões anteriores desta biblioteca, bem como para simplificar a especificação de claim_specs baseadas em períodos, você pode usar, em vez de qualquer único parâmetro claim_spec, uma tabela de validation_options. O parâmetro deve ser expresso como uma tabela de chave/valor. Cada chave da tabela deve ser escolhida a partir da seguinte lista.
Ao usar validation_options legadas, você DEVE SOMENTE especificar essas opções. Ou seja, você não pode misturar validation_options legadas com outros validadores claim_spec. Para conseguir isso, você deve especificar várias opções para as funções jwt:load/jwt:verify_jwt_obj.
-
lifetime_grace_period: Defina a margem de manobra em segundos para levar em conta a discrepância de relógio entre o servidor que gerou o jwt e o servidor que o valida. O valor deve ser zero (0) ou um inteiro positivo.-
Quando esta opção de validação é especificada, o processo garantirá que o jwt contenha pelo menos uma das duas reivindicações
nbfouexpe compare o tempo atual do relógio contra esses limites. Se o jwt for considerado expirado ou não válido ainda, a verificação falhará. -
Quando nenhuma das reivindicações
nbfeexppuder ser encontrada, a verificação falhará. -
As reivindicações
nbfeexpdevem ser expressas no jwt como valores numéricos. Se não for esse o caso, a verificação falhará. -
Especificar esta opção é equivalente a chamar:
validators.set_system_leeway(leeway)
e especificar como um
claim_spec:{ __jwt = validators.require_one_of({ "nbf", "exp" }), nbf = validators.opt_is_not_before(), exp = validators.opt_is_not_expired() } -
-
require_nbf_claim: Expresse se a reivindicaçãonbfé opcional ou não. O valor deve ser um booleano.-
Quando esta opção de validação é definida como
truee nenhumalifetime_grace_periodfoi especificada, uma margem de manobra de zero (0) é implícita. -
Especificar esta opção é equivalente a especificar como um
claim_spec:{ nbf = validators.is_not_before(), }
-
-
require_exp_claim: Expresse se a reivindicaçãoexpé opcional ou não. O valor deve ser um booleano.-
Quando esta opção de validação é definida como
truee nenhumalifetime_grace_periodfoi especificada, uma margem de manobra de zero (0) é implícita. -
Especificar esta opção é equivalente a especificar como um
claim_spec:{ exp = validators.is_not_expired(), }
-
-
valid_issuers: Lista branca dos emissores verificados do jwt. O valor deve ser um array de strings.-
Quando esta opção de validação é especificada, o processo comparará a reivindicação
issdo jwt com a lista de emissores válidos. A comparação é feita de forma sensível a maiúsculas e minúsculas. Se o emissor do jwt não for encontrado na lista branca, a verificação falhará. -
A reivindicação
issdeve ser expressa no jwt como uma string. Se não for esse o caso, a verificação falhará. -
Especificar esta opção é equivalente a especificar como um
claim_spec:{ iss = validators.equals_any_of(valid_issuers), }
-
exemplo de uso de 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" }
}
)
Exemplos
Testando Com Docker
./ci script
Veja Também
- o módulo ngx_lua: http://wiki.nginx.org/HttpLuaModule
GitHub
Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-jwt.