Pular para conteúdo

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,

  1. 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;;";.
  2. você usa require para 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 nbf ou exp e 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 nbf e exp puder ser encontrada, a verificação falhará.

    • As reivindicações nbf e exp devem 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ção nbf é opcional ou não. O valor deve ser um booleano.

    • Quando esta opção de validação é definida como true e nenhuma lifetime_grace_period foi 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ção exp é opcional ou não. O valor deve ser um booleano.

    • Quando esta opção de validação é definida como true e nenhuma lifetime_grace_period foi 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 iss do 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 iss deve 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

GitHub

Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-jwt.