Aller au contenu

jwt: JWT Pour Le Grand nginx-module-lua

Installation

Si vous n'avez pas configuré l'abonnement au dépôt RPM, inscrivez-vous. Ensuite, vous pouvez procéder avec les étapes suivantes.

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

Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que le nginx-module-lua est installé.

Ce document décrit lua-resty-jwt v0.1.11 publié le 11 juillet 2017.


Cette bibliothèque nécessite une version d'nginx avec OpenSSL, le module ngx_lua, le LuaJIT 2.0, le lua-resty-hmac, et le lua-resty-string.

Synopsis

    # 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éthodes

Pour charger cette bibliothèque,

  1. vous devez spécifier le chemin de cette bibliothèque dans la directive lua_package_path de ngx_lua. Par exemple, lua_package_path "/path/to/lua-resty-jwt/lib/?.lua;;";.
  2. vous utilisez require pour charger la bibliothèque dans une variable Lua locale :
    local jwt = require "resty.jwt"

sign

syntax: local jwt_token = jwt:sign(key, table_of_jwt)

signe un table_of_jwt en un jwt_token.

L'argument alg spécifie quel algorithme de hachage utiliser (HS256, HS512, RS256).

exemple de table_of_jwt

{
    "header": {"typ": "JWT", "alg": "HS512"},
    "payload": {"foo": "bar"}
}

verify

syntax: local jwt_obj = jwt:verify(key, jwt_token [, claim_spec [, ...]])

vérifie un jwt_token et renvoie un tableau jwt_obj. key peut être une clé prépartagée (sous forme de chaîne), ou une fonction qui prend un seul paramètre (la valeur de kid de l'en-tête) et renvoie soit la clé prépartagée (sous forme de chaîne) pour le kid, soit nil si la recherche du kid a échoué. Cet appel échouera si vous essayez de spécifier une fonction pour key et qu'il n'y a pas de kid existant dans l'en-tête.

Voir Vérification pour des détails sur le format des paramètres 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

charge le jwt, vérifie le kid, puis le vérifie avec la clé correcte.

exemple 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)

signe un table_of_jwt en un jwt_token.

L'argument alg spécifie quel algorithme de hachage utiliser pour chiffrer la clé (dir). L'argument enc spécifie quel algorithme de hachage utiliser pour chiffrer la charge utile (A128CBC-HS256, A256CBC-HS512).

exemple de table_of_jwt

{
    "header": {"typ": "JWE", "alg": "dir", "enc":"A128CBC-HS256"},
    "payload": {"foo": "bar"}
}

Vérification

Les fonctions jwt:load et jwt:verify_jwt_obj prennent, comme paramètres supplémentaires, un nombre quelconque de paramètres claim_spec optionnels. Un claim_spec est simplement une table lua de revendications et de validateurs. Chaque clé dans la table claim_spec correspond à une clé correspondante dans la charge utile, et le validator est une fonction qui sera appelée pour déterminer si les revendications sont satisfaites.

La signature d'une fonction validator est :

function(val, claim, jwt_json)

val est la valeur de la revendication de l'jwt_obj en cours de test (ou nil si elle n'existe pas dans la charge utile de l'objet), claim est le nom de la revendication qui est vérifiée, et jwt_json est une représentation sérialisée en json de l'objet qui est vérifié. Si la fonction n'a pas besoin des paramètres claim ou jwt_json, ils peuvent être omis.

Une fonction validator renvoie soit true, soit false. Tout validator PEUT lever une erreur, et la validation sera considérée comme un échec, et l'erreur qui a été levée sera mise dans le champ de raison de l'objet résultant. Si un validator ne renvoie rien (c'est-à-dire nil), alors la fonction est considérée comme ayant réussi - sous l'hypothèse qu'elle aurait levé une erreur si elle avait échoué.

Une revendication spéciale nommée __jwt peut être utilisée de sorte que si une fonction validator existe pour elle, alors le validator sera appelé avec un clone profond de l'objet jwt analysé entier comme valeur de val. Cela vous permet d'écrire des vérifications pour un objet entier qui peuvent dépendre d'une ou plusieurs revendications.

Plusieurs tables claim_spec peuvent être spécifiées pour les fonctions jwt:load et jwt:verify_jwt_obj - et elles seront exécutées dans l'ordre. Il n'y a aucune garantie sur l'ordre d'exécution des validators individuels au sein d'un seul claim_spec. Si un claim_spec échoue, alors tout claim_spec suivant NE SERA PAS exécuté.

exemple 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
}

Validateurs JWT

Une bibliothèque de fonctions validator utiles existe dans resty.jwt-validators. Vous pouvez utiliser cette bibliothèque en incluant :

local validators = require "resty.jwt-validators"

Les fonctions suivantes sont actuellement définies dans la bibliothèque de validateurs. Celles marquées par "(opt)" signifient que la même fonction existe sous le nom opt_<name> qui prend les mêmes paramètres. La version "opt" de la fonction renverra true si la clé n'existe pas dans la charge utile de l'objet jwt en cours de vérification, tandis que la version "non-opt" de la fonction renverra false si la clé n'existe pas dans la charge utile de l'objet jwt en cours de vérification.

validators.chain(...)

Renvoie un validateur qui enchaîne les fonctions données, les unes après les autres - tant qu'elles continuent à passer leurs vérifications.

validators.required(chain_function)

Renvoie un validateur qui renvoie false si une valeur n'existe pas. Si la valeur existe et qu'une chain_function est spécifiée, alors la valeur de chain_function(val, claim, jwt_json) sera renvoyée, sinon, true sera renvoyé. Cela permet de spécifier qu'une valeur est à la fois requise et qu'elle doit correspondre à une vérification supplémentaire.

validators.require_one_of(claim_keys)

Renvoie un validateur qui renvoie une erreur avec un message si AUCUNE des clés de revendication données n'existe. Il est attendu que cette fonction soit utilisée contre un objet jwt complet. Les claim_keys doivent être une table de chaînes non vide.

validators.check(check_val, check_function, name, check_type) (opt)

Renvoie un validateur qui vérifie si le résultat de l'appel de la check_function donnée pour la valeur testée et check_val renvoie true. La valeur de check_val et check_function ne peut pas être nil. Le nom optionnel est utilisé pour les messages d'erreur et est par défaut "check_value". Le type de vérification optionnel est utilisé pour s'assurer que le type de vérification correspond et est par défaut type(check_val). Le premier paramètre passé à check_function ne sera jamais nil. Si la check_function lève une erreur, cela sera ajouté au message d'erreur.

validators.equals(check_val) (opt)

Renvoie un validateur qui vérifie si une valeur est exactement égale (en utilisant ==) à la valeur de vérification donnée. La valeur de check_val ne peut pas être nil.

validators.matches(pattern) (opt)

Renvoie un validateur qui vérifie si une valeur correspond au motif donné (en utilisant string.match). La valeur de pattern doit être une chaîne.

validators.any_of(check_values, check_function, name, check_type, table_type) (opt)

Renvoie un validateur qui appelle la check_function donnée pour chacune des check_values données et la valeur testée. Si l'un de ces appels renvoie true, alors cette fonction renvoie true. La valeur de check_values doit être une table non vide avec tous les mêmes types, et la valeur de check_function ne doit pas être nil. Le nom optionnel est utilisé pour les messages d'erreur et est par défaut "check_values". Le type de vérification optionnel est utilisé pour s'assurer que le type de vérification correspond et est par défaut type(check_values[1]) - le type de table.

validators.equals_any_of(check_values) (opt)

Renvoie un validateur qui vérifie si une valeur est exactement égale à l'une des check_values données.

validators.matches_any_of(patterns) (opt)

Renvoie un validateur qui vérifie si une valeur correspond à l'un des patterns donnés.

validators.contains_any_of(check_values,name) (opt)

Renvoie un validateur qui vérifie si une valeur de type attendu string existe dans l'une des check_values données. La valeur de check_values doit être une table non vide avec tous les mêmes types. Le nom optionnel est utilisé pour les messages d'erreur et est par défaut check_values.

validators.greater_than(check_val) (opt)

Renvoie un validateur qui vérifie comment une valeur se compare (numériquement, en utilisant >) à une check_value donnée. La valeur de check_val ne peut pas être nil et doit être un nombre.

validators.greater_than_or_equal(check_val) (opt)

Renvoie un validateur qui vérifie comment une valeur se compare (numériquement, en utilisant >=) à une check_value donnée. La valeur de check_val ne peut pas être nil et doit être un nombre.

validators.less_than(check_val) (opt)

Renvoie un validateur qui vérifie comment une valeur se compare (numériquement, en utilisant <) à une check_value donnée. La valeur de check_val ne peut pas être nil et doit être un nombre.

validators.less_than_or_equal(check_val) (opt)

Renvoie un validateur qui vérifie comment une valeur se compare (numériquement, en utilisant <=) à une check_value donnée. La valeur de check_val ne peut pas être nil et doit être un nombre.

validators.is_not_before() (opt)

Renvoie un validateur qui vérifie si l'heure actuelle n'est pas avant la valeur testée dans la marge de manœuvre du système. Cela signifie que :

val <= (system_clock() + system_leeway).

validators.is_not_expired() (opt)

Renvoie un validateur qui vérifie si l'heure actuelle n'est pas égale ou après la valeur testée dans la marge de manœuvre du système. Cela signifie que :

val > (system_clock() - system_leeway).

validators.is_at() (opt)

Renvoie un validateur qui vérifie si l'heure actuelle est la même que la valeur testée dans la marge de manœuvre du système. Cela signifie que :

val >= (system_clock() - system_leeway) and val <= (system_clock() + system_leeway).

validators.set_system_leeway(leeway)

Une fonction pour définir la marge de manœuvre (en secondes) utilisée pour is_not_before et is_not_expired. La valeur par défaut est d'utiliser 0 secondes.

validators.set_system_clock(clock)

Une fonction pour définir l'horloge système utilisée pour is_not_before et is_not_expired. La valeur par défaut est d'utiliser ngx.now.

exemple de claim_spec utilisant des validateurs

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" })
}

Options Legacy/Temps

Afin de prendre en charge le code qui utilisait des versions précédentes de cette bibliothèque, ainsi que pour simplifier la spécification des claim_specs basées sur le temps, vous pouvez utiliser à la place de tout paramètre claim_spec unique une table d'validation_options. Le paramètre doit être exprimé sous forme de table clé/valeur. Chaque clé de la table doit être choisie dans la liste suivante.

Lorsque vous utilisez les validation_options legacy, vous DEVEZ UNIQUEMENT spécifier ces options. C'est-à-dire que vous ne pouvez pas mélanger les validation_options legacy avec d'autres validateurs claim_spec. Pour y parvenir, vous devez spécifier plusieurs options aux fonctions jwt:load/jwt:verify_jwt_obj.

  • lifetime_grace_period: Définir la marge de manœuvre en secondes pour tenir compte du décalage horaire entre le serveur qui a généré le jwt et le serveur qui le valide. La valeur doit être zéro (0) ou un entier positif.

    • Lorsque cette option de validation est spécifiée, le processus s'assurera que le jwt contient au moins l'une des deux revendications nbf ou exp et comparera l'heure actuelle à ces limites. Si le jwt est considéré comme expiré ou non valide, la vérification échouera.

    • Lorsque aucune des revendications nbf et exp ne peut être trouvée, la vérification échouera.

    • Les revendications nbf et exp doivent être exprimées dans le jwt sous forme de valeurs numériques. Si ce n'est pas le cas, la vérification échouera.

    • Spécifier cette option équivaut à appeler :

      validators.set_system_leeway(leeway)
      

    et à spécifier comme claim_spec :

    {
      __jwt = validators.require_one_of({ "nbf", "exp" }),
      nbf = validators.opt_is_not_before(),
      exp = validators.opt_is_not_expired()
    }
    

  • require_nbf_claim: Exprime si la revendication nbf est optionnelle ou non. La valeur doit être un booléen.

    • Lorsque cette option de validation est définie sur true et qu'aucun lifetime_grace_period n'a été spécifié, une marge de manœuvre de zéro (0) est implicite.

    • Spécifier cette option équivaut à spécifier comme claim_spec :

      {
        nbf = validators.is_not_before(),
      }
      

  • require_exp_claim: Exprime si la revendication exp est optionnelle ou non. La valeur doit être un booléen.

    • Lorsque cette option de validation est définie sur true et qu'aucun lifetime_grace_period n'a été spécifié, une marge de manœuvre de zéro (0) est implicite.

    • Spécifier cette option équivaut à spécifier comme claim_spec :

      {
        exp = validators.is_not_expired(),
      }
      

  • valid_issuers: Liste blanche des émetteurs vérifiés du jwt. La valeur doit être un tableau de chaînes.

    • Lorsque cette option de validation est spécifiée, le processus comparera la revendication iss du jwt à la liste des émetteurs valides. La comparaison se fait de manière sensible à la casse. Si l'émetteur du jwt n'est pas trouvé dans la liste blanche, la vérification échouera.

    • La revendication iss doit être exprimée dans le jwt sous forme de chaîne. Si ce n'est pas le cas, la vérification échouera.

    • Spécifier cette option équivaut à spécifier comme claim_spec :

      {
        iss = validators.equals_any_of(valid_issuers),
      }
      

exemple d'utilisation des 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-trusted-issuer" }
    }
)

Exemples

Test Avec Docker

./ci script

Voir Aussi

GitHub

Vous pouvez trouver des conseils de configuration supplémentaires et de la documentation pour ce module dans le dépôt GitHub pour nginx-module-jwt.