Aller au contenu

jwt-verification: Bibliothèque de vérification JWT pour nginx-module-lua avec intégration JWKS

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-verification

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-verification

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

Ce document décrit lua-resty-jwt-verification v0.7.0 publié le 30 octobre 2025.


Bibliothèque de vérification JWT pour OpenResty.

OPM LuaRocks Tests

Description

Bibliothèque de vérification JWT pour OpenResty.

L'objectif du projet est d'être un remplacement moderne et plus léger pour lua-resty-jwt avec un support intégré pour JWKS.

Ce projet ne fournit pas de fonctionnalités de manipulation ou de création de JWT : vous pouvez uniquement vérifier/déchiffrer les jetons.

Objectifs non visés par la bibliothèque

  • Création/modification de JWT
  • Fonctionnalité complète pour l'exhaustivité des RFC.
  • Les fonctionnalités RFC sans sens et non sécurisées (par exemple, alg none) ne seront pas mises en œuvre.

Différences par rapport à lua-resty-jwt

Les principales différences sont : - Pas de manipulation de JWT de quelque nature que ce soit (vous ne pouvez que les déchiffrer/vérifier). - Structure interne plus simple reposant sur des versions plus récentes de lua-resty-openssl et OpenSSL. - Prend en charge différents algorithmes JWE (voir les tableaux ci-dessus). - Vérification automatique des JWT donnée un point de terminaison HTTP JWKS.

Si l'un des points ci-dessus pose problème, ou si vous avez besoin de compatibilité avec des versions plus anciennes d'OpenResty, je recommande de rester avec lua-resty-jwt.

Types

Les types et les vérifications nulles sont fournis avec une utilisation extensive des annotations EmmyLua.

Intégrations de plugins IDE pour EmmyLua : - Idea - VSCode

Le fichier ngx.d.lua à la racine du projet fournit quelques stubs ngx.

Fonctionnalités prises en charge

  • Vérification JWS : avec des clés symétriques ou asymétriques.
  • Déchiffrement JWE : avec des clés symétriques ou asymétriques.
  • Formats de clés asymétriques pris en charge :
  • PEM
  • DER
  • JWK
  • Validation des revendications JWT.
  • Récupération automatique de JWKS et validation de JWT.
  • stratégies de mise en cache optionnelles.
  • JWT imbriqués (JWS dans JWE)

Vérification JWS

Revendications Implémenté
alg ✅
jku ❌
jwk ❌
kid ✅
x5u ❌
x5c ❌
x5t ❌
x5t#S256 ❌
typ ✅
cty ❌
crit ✅
alg Implémenté Exigences de mise en œuvre JOSE Exigences
HS256 ✅ Requis
HS384 ✅ Optionnel
HS512 ✅ Optionnel
RS256 ✅ Recommandé
RS384 ✅ Optionnel
RS512 ✅ Optionnel
ES256 ✅ Recommandé+
ES384 ✅ Optionnel
ES512 ✅ Optionnel
PS256 ✅ Optionnel
PS384 ✅ Optionnel
PS512 ✅ Optionnel
none ❌ Optionnel
EdDSA ❌ Obsolète
ES256K ✅ Optionnel
Ed25519 ✅ Optionnel *OpenSSL 3.0+
Ed448 ✅ Optionnel

Déchiffrement JWE

Revendications Implémenté
alg ✅
enc ✅
zip ❌
jku ❌
jwk ❌
kid ✅
x5u ❌
x5c ❌
x5t ❌
x5t#S256 ❌
typ ✅
cty ✅
crit ✅
kty Implémenté Exigences de mise en œuvre JOSE
EC ✅ Recommandé+
RSA ✅ Requis
oct ✅ Requis
OKP ✅ Optionnel
alg Implémenté Exigences de mise en œuvre JOSE Exigences
RSA1_5 ❌ Recommandé-
RSA-OAEP ✅ Recommandé+
RSA-OAEP-256 ✅ Optionnel
RSA-OAEP-384 ✅ Optionnel
RSA-OAEP-512 ✅ Optionnel
A128KW ✅ Recommandé *OpenSSL 3.0+
A192KW ✅ Optionnel *OpenSSL 3.0+
A256KW ✅ Recommandé *OpenSSL 3.0+
dir ✅ Recommandé
ECDH-ES ✅ Recommandé+
ECDH-ES+A128KW ✅ Recommandé *OpenSSL 3.0+
ECDH-ES+A192KW ✅ Optionnel *OpenSSL 3.0+
ECDH-ES+A256KW ✅ Recommandé *OpenSSL 3.0+
A128GCMKW ❌ Optionnel
A192GCMKW ❌ Optionnel
A256GCMKW ❌ Optionnel
PBES2-HS256+A128KW ❌ Optionnel
PBES2-HS384+A192KW ❌ Optionnel
PBES2-HS512+A256KW ❌ Optionnel

*La première version officielle d'OpenResty incluant OpenSSL 3.0+ est OpenResty 1.27.1.1 qui a été livrée avec OpenSSL 3.0.15 (Oui, la terriblement lente série OpenSSL 3.0...).

Donc, s'il vous plaît, optez pour OpenResty 1.27.1.2 comme minimum, qui a été livré avec OpenSSL 3.4.1.

enc Implémenté Exigences de mise en œuvre JOSE
A128CBC-HS256 ✅ Requis
A192CBC-HS384 ✅ Optionnel
A256CBC-HS512 ✅ Requis
A128GCM ✅ Recommandé
A192GCM ✅ Optionnel
A256GCM ✅ Recommandé

Stratégies de mise en cache de récupération JWKS

Stratégie de cache Implémenté
pas de cache ✅
local (shared_dict) ✅

Utilisation de la vérification JWT

jwt.decode_header_unsafe

syntax: header, err = jwt.decode_header_unsafe(token)

Lire un en-tête jwt et le convertir en table lua.

Important : cette méthode ne valide pas la signature JWT ! Utilisez-la uniquement si vous avez besoin d'inspecter l'en-tête du jeton sans avoir à effectuer la validation complète.

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NDkwNzJ9._MwFdsBPSyci9iARpoAaulReGcn1q7mKiPZjR2JDvdY"
local header, err = jwt.decode_header_unsafe(token)
if not header then
    return nil, "jwt malformé : " .. err
end
print("alg: " .. header.alg) -- alg: HS256

jwt.verify

syntax: decoded_token, err = jwt.verify(token, secret, options?)

Valider un jeton JWS et le convertir en table lua.

Le paramètre optionnel options peut être passé pour configurer le validateur de jetons. Les champs valides sont : - valid_signing_algorithms (dict | nil) : un dictionnaire contenant les revendications alg autorisées utilisées pour valider le JWT. - typ (string | nil) : si non nul, assurez-vous que la revendication JWT typ correspond à la valeur passée. - issuer (string | nil) : si non nul, assurez-vous que la revendication JWT iss correspond à la valeur passée. - audiences (string | table | nil) : si non nul, assurez-vous que la revendication JWT aud correspond à l'une des valeurs fournies. - subject (string | nil) : si non nul, assurez-vous que la revendication JWT sub correspond à la valeur passée. - jwtid (string | nil) : si non nul, assurez-vous que la revendication JWT jti correspond à la valeur passée. - ignore_not_before (bool) : Si vrai, la revendication JWT nbf sera ignorée. - ignore_expiration (bool) : Si vrai, la revendication JWT exp sera ignorée. - current_unix_timestamp (datetime | nil) : les revendications JWT nbf et exp seront validées par rapport à cet horodatage. Si nul, utilisera la date et l'heure actuelles fournies par ngx.time(). - timestamp_skew_seconds (int) : Combien de secondes de marge la bibliothèque peut-elle utiliser pour vérifier l'expiration du jeton par rapport à l'heure actuelle. Utile lorsque les horloges ne sont pas toujours exactement synchronisées. Définir cette valeur trop élevée peut poser des problèmes de sécurité.

Valeurs par défaut pour les champs options :

local verify_default_options = {
    valid_signing_algorithms = {
        ["HS256"]="HS256", ["HS384"]="HS384", ["HS512"]="HS512",
        ["RS256"]="RS256", ["RS384"]="RS384", ["RS512"]="RS512",
        ["ES256"]="ES256", ["ES384"]="ES384", ["ES512"]="ES512",
        ["PS256"]="PS256", ["PS384"]="PS384", ["PS512"]="PS512",
        ["ES256K"]="ES256K", ["Ed25519"]="Ed25519", ["Ed448"]="Ed448",
    },
    typ = nil,
    issuer = nil,
    audiences = nil,
    subject = nil,
    jwtid = nil,
    ignore_not_before = false,
    ignore_expiration = false,
    current_unix_timestamp = nil,
    timestamp_skew_seconds = 1,
}

Exemple minimal avec des clés symétriques :

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NTUwMTV9.NuEhIzUuufJgPZ8CmCPnD4Vrw7EnTyWD8bGtYCwuDZ0"
local decoded_token, err = jwt.verify(token, "superSecretKey")
if not decoded_token then
    return nil, "jwt invalide : " .. err
end
print(decoded_token.header.alg) -- HS256
print(decoded_token.payload.foo) -- bar

Exemple minimal avec des clés asymétriques :

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2Njg2Mzd9.H6PE-zLizMMqefx8DG4X5glVjyxR9UNT225Tq2yufHhu4k9K0IGttpykjMCG8Ck_4Qt2ezEWIgoiWhSn1rv_zwxe7Pv-B09fDs7h1hbASi5MZ0YVAmK9ID1RCKM_NTBEnPLot_iopKZRj2_J5F7lvXwJDZSzEAFJZdrgjKeBS4saDZAv7SIL9Nk75rdhgY-RgRwsjmTYSksj7eioRJJLHifrMnlQDbdrBD5_Qk5tD6VPcssO-vIVBUAYrYYTa7M7A_v47UH84zDtzNYBbk9NrDbyq5-tYs0lZwNhIX8t-0VAxjuCyrrGZvv8_O01pdi90kQmntFIbaiDiD-1WlGcGA"
local decoded_token, err = jwt.verify(token, "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXFhNyhFWuWtFSJqfOAw\np42lLIn9kB9oaciiKgNAYZ8SYw5t9Fo+Zh7IciVijn+cVS2/aoBNg2HhfdYgfpQ/\nsb6jwbRqFMln2GmG+X2aJ2wXMJ/QfxrPFdO9L36bAEwkubUTYXwgMSm1KqWRN8xX\n+oBu+dbyzw7iUbrmw0ybzXKZLJvetCvmt0reU5TvdwoczOWFBSKeYnzBrC6hISD8\n8TYDJ4tiw1EWVOupQGqgel0KjC7iwdIYi7PROn6/1MMnF48zlBbT/7/zORj84Z/y\nDnmxZu1MQ07kHqXDRYumSfCerg5Xw5vde7Tz8O0TWtaYV3HJXNa0VpN5OI3L4y7P\nhwIDAQAB\n-----END PUBLIC KEY-----")
if not decoded_token then
    return nil, "jwt invalide : " .. err
end
print(decoded_token.header.alg) -- RS256
print(decoded_token.payload.foo) -- bar

Exemples avec des options personnalisées :

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE3MTY2NTUwMTV9.NuEhIzUuufJgPZ8CmCPnD4Vrw7EnTyWD8bGtYCwuDZ0"
local decoded_token, err = jwt.verify(token, "superSecretKey", {
    valid_signing_algorithms = {["HS256"]="HS256", ["HS384"]="HS384", ["HS512"]="HS512"}, -- n'autoriser que les algorithmes de la famille HS
    audiences = {"user", "admin"}, -- `aud` doit être l'une des valeurs suivantes
    ignore_not_before = true -- ignorer la revendication `nbf` (non recommandé)
})
if not decoded_token then
    return nil, "jwt invalide : " .. err
end
print(decoded_token.header.alg) -- HS256
print(decoded_token.payload.foo) -- bar

jwt.decrypt

syntax: decoded_token, err = jwt.decrypt(token, secret, options?)

Déchiffrer et valider un jeton JWE et le convertir en table lua.

Le paramètre optionnel options peut être passé pour configurer le validateur de jetons. Les champs valides sont : - valid_encryption_alg_algorithms (dict | nil) : un dictionnaire contenant les revendications alg autorisées utilisées pour déchiffrer le JWT. - valid_encryption_enc_algorithms (dict | nil) : un dictionnaire contenant les revendications enc autorisées utilisées pour déchiffrer le JWT. - typ (string | nil) : si non nul, assurez-vous que la revendication JWT typ correspond à la valeur passée. - issuer (string | nil) : si non nul, assurez-vous que la revendication JWT iss correspond à la valeur passée. - audiences (string | table | nil) : si non nul, assurez-vous que la revendication JWT aud correspond à l'une des valeurs fournies. - subject (string | nil) : si non nul, assurez-vous que la revendication JWT sub correspond à la valeur passée. - jwtid (string | nil) : si non nul, assurez-vous que la revendication JWT jti correspond à la valeur passée. - ignore_not_before (bool) : Si vrai, la revendication JWT nbf sera ignorée. - ignore_expiration (bool) : Si vrai, la revendication JWT exp sera ignorée. - current_unix_timestamp (datetime | nil) : les revendications JWT nbf et exp seront validées par rapport à cet horodatage. Si nul, utilisera la date et l'heure actuelles fournies par ngx.time(). - timestamp_skew_seconds (int) : Combien de secondes de marge la bibliothèque peut-elle utiliser pour vérifier l'expiration du jeton par rapport à l'heure actuelle. Utile lorsque les horloges ne sont pas toujours exactement synchronisées. Définir cette valeur trop élevée peut poser des problèmes de sécurité. - allow_nested_jwt (bool) : Permet la vérification des JWT contenant un autre JWT (c'est-à-dire des JWT imbriqués ou JWT dans JWT). Cela est opt-in par défaut puisque les revendications à valider sont toujours à l'intérieur du JWT le plus interne et NE SERONT PAS automatiquement validées. Il vous appartient de valider récursivement les JWT internes renvoyés en tant que chaîne dans le champ payload par cette bibliothèque. Un JWT imbriqué DOIT contenir la clé d'en-tête cty définie sur JWT pour être reconnu comme tel.

Valeurs par défaut pour les champs options :

local decrypt_default_options = {
    valid_encryption_alg_algorithms = {
        ["RSA-OAEP"]="RSA-OAEP",
        ["RSA-OAEP-256"]="RSA-OAEP-256", ["RSA-OAEP-384"]="RSA-OAEP-384", ["RSA-OAEP-512"]="RSA-OAEP-512",
        ["A128KW"]="A128KW", ["A192KW"]="A192KW", ["A256KW"]="A256KW",
        ["dir"]="dir",
        ["ECDH-ES"]="ECDH-ES",
        ["ECDH-ES+A128KW"]="ECDH-ES+A128KW",
        ["ECDH-ES+A192KW"]="ECDH-ES+A192KW",
        ["ECDH-ES+A256KW"]="ECDH-ES+A256KW",
    },
    valid_encryption_enc_algorithms = {
        ["A128CBC-HS256"]="A128CBC-HS256",
        ["A192CBC-HS384"]="A192CBC-HS384",
        ["A256CBC-HS512"]="A256CBC-HS512",
        ["A128GCM"]="A128GCM",
        ["A192GCM"]="A192GCM",
        ["A256GCM"]="A256GCM",
    },
    typ = nil,
    issuer = nil,
    audiences = nil,
    subject = nil,
    jwtid = nil,
    ignore_not_before = false,
    ignore_expiration = false,
    current_unix_timestamp = nil,
    timestamp_skew_seconds = 1,
    allow_nested_jwt = false,
}

Exemple minimal avec des clés symétriques :

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.zAIq7qVAEO-eCG6gOdd3ld8_IHzeq3UlaWLHF2IDn6nNUuHh5n_i4w.5CM864cgiBgFPwluW4ViRg.mUeX7zHDVNsXhys0XO5S4w.t3yAR_HU0GDTEyCbpRa6BQ"
local decoded_token, err = jwt.decrypt(token, "superSecretKey12")
if not decoded_token then
    return nil, "jwt invalide : " .. err
end
print(decoded_token.header.alg) -- A128KW
print(decoded_token.header.enc) -- A128CBC-HS256
print(decoded_token.payload.foo) -- bar

Exemple minimal avec des clés asymétriques au format PEM :

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkEyNTZHQ00iLCJlcGsiOnsieCI6IkFJdkVhSzVKZGl6d1I5ZFMzRUN2Y0dKMGNHWXNFejdpYWJwRUp1bE0tWDAiLCJjcnYiOiJYMjU1MTkiLCJrdHkiOiJPS1AifX0.QFfmPVYjk1PoyhE7elaDgUdUGGeAECLo7jB4ghq_8MIRXV3VKO1yAA.XITF2apHB5roeUsx.08T0gALwkb6Wibr2Og.IJoh3U_tspnMx_mWelRT5g"
local decoded_token, err = jwt.decrypt(token, "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VuBCIEIMCxXl/FEuh3pGo1Z++QRs2vudqkGd63mK0Js0f6y+55\n-----END PRIVATE KEY-----", nil)
if not decoded_token then
    return nil, "jwt invalide : " .. err
end
print(decoded_token.header.alg) -- ECDH-ES+A128KW
print(decoded_token.header.enc) -- A256GCM
print(decoded_token.payload.foo) -- bar

Exemples avec des options personnalisées :

local jwt = require("resty.jwt-verification")

local token = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.zAIq7qVAEO-eCG6gOdd3ld8_IHzeq3UlaWLHF2IDn6nNUuHh5n_i4w.5CM864cgiBgFPwluW4ViRg.mUeX7zHDVNsXhys0XO5S4w.t3yAR_HU0GDTEyCbpRa6BQ"
local decoded_token, err = jwt.decrypt(token, "superSecretKey12", {
    valid_encryption_alg_algorithms = {["A128KW"]="A128KW"}, -- n'autoriser que les algorithmes de la famille A128KW (nécessite OpenSSL 3.0+)
    valid_encryption_enc_algorithms = {["A128CBC-HS256"]="A128CBC-HS256"}, -- n'autoriser que les algorithmes de la famille A128CBC
    audiences = {"user", "admin"}, -- `aud` doit être l'une des valeurs suivantes
    ignore_not_before = true -- ignorer la revendication `nbf` (non recommandé)
})
if not decoded_token then
    return nil, "jwt invalide : " .. err
end
print(decoded_token.header.alg) -- A128KW
print(decoded_token.header.enc) -- A128CBC-HS256
print(decoded_token.payload.foo) -- bar

Utilisation de la vérification JWKS

Le module resty.jwt-verification-jwks implémente la récupération automatique de JWKS à partir d'un point de terminaison HTTP et la validation subséquente de JWT avec les clés récupérées.

Les modules resty.jwt-verification-jwks-cache-* implémentent des stratégies de mise en cache JWKS optionnelles. Une seule stratégie de mise en cache peut être activée à la fois ; si aucune n'est activée, le point de terminaison JWKS sera appelé une fois pour chaque JWT à valider.

jwks.init

syntax: ok, err = jwks.init(cache_strategy?)

Initialiser le module jwks et éventuellement spécifier une stratégie de mise en cache.

Cette fonction doit être appelée une seule fois et de préférence dans la section init_by_lua_file.

local jwks = require("resty.jwt-verification-jwks")

-- initialiser sans cache
local ok, err = jwks.init(nil)
if not ok then
    ngx.say("Erreur lors de l'initialisation du module jwks : ", err)
end

-- ou ...

-- initialiser avec un cache local basé sur le dictionnaire de mémoire partagée d'openresty.
-- ajoutez ceci dans la section `http` de votre configuration nginx : `lua_shared_dict resty_jwt_verification_cache_jwks 10m;`
-- voir https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/#ngxshareddict
local jwks_cache_local = require("resty.jwt-verification-jwks-cache-local")
local ok, err = jwks.init(jwks_cache_local)
if not ok then
    ngx.say("Erreur lors de l'initialisation du module jwks : ", err)
end

Vous pouvez implémenter votre propre cache et le passer dans la méthode d'initialisation à la place. Voici un exemple de comment :

local my_cache = {}

---Obtenir la chaîne d'entrée mise en cache pour la clé.
---@param key string Clé de cache.
---@return string|nil value Retourner le résultat mis en cache sous forme de chaîne s'il est présent, nil sinon.
function my_cache.get(key)
    -- TODO
end

---Mettre en cache les données sous la clé jusqu'à expiration.
---@param key string Clé de cache.
---@param value string Valeur de cache.
---@param expiry integer Expiration de l'entrée de cache en secondes.
---@return boolean|nil ok true en cas de succès
---@return string|nil err nil en cas de succès, message d'erreur sinon.
function my_cache.setex(key, value, expiry)
    -- TODO
end

local ok, err = jwks.init(my_cache)
if not ok then
    ngx.say("Erreur lors de l'initialisation du module jwks : ", err)
end

jwks.set_http_timeouts_ms

syntax: jwks.set_http_timeouts_ms(connect, send, read)

Définir les délais d'attente du client HTTP en millisecondes utilisés pour récupérer JWKS.

local jwks = require("resty.jwt-verification-jwks")

jwks.set_http_timeouts_ms(5000, 5000, 5000)

jwks.set_http_ssl_verify

syntax: jwks.set_http_ssl_verify(enabled)

Activer/désactiver la vérification TLS utilisée par le client HTTP pour récupérer JWKS.

Par défaut, tous les certificats TLS sont vérifiés. Si le point de terminaison JWKS utilise des certificats auto-signés, ajoutez soit la CA racine respective au magasin de certificats de l'OS, soit désactivez la vérification des certificats avec ce point de terminaison (ce qui est dangereux).

local jwks = require("resty.jwt-verification-jwks")

jwks.set_http_ssl_verify(false)

jwks.set_cache_ttl

syntax: jwks.set_http_ssl_verify(enabled)

Changer la durée de vie par défaut du cache. La valeur par défaut est de 12 heures.

Remarque : La durée de vie du cache ne peut être utilisée que lorsque le module jwks a été initialisé avec un cache. Voir comment activer la mise en cache.

local jwks = require("resty.jwt-verification-jwks")

jwks.set_cache_ttl(2 * 3600) -- 2h

jwks.fetch_jwks

syntax: payload, err = jwks.fetch_jwks(endpoint)

Récupérer manuellement JWKS à partir d'un point de terminaison HTTP ; le payload retourné, en cas de succès, est le corps de la réponse HTTP sous forme de chaîne : Aucune vérification n'est effectuée pour savoir si le payload contient JWKS ou autre chose.

Si une stratégie de mise en cache a été activée, le point de terminaison essaiera d'abord de le récupérer à partir du cache. Après un échec de cache et une récupération réussie de JWKS via HTTP, le cache sera mis à jour avec le résultat.

local jwks = require("resty.jwt-verification-jwks")

payload, err = jwks.fetch_jwks("https://www.googleapis.com/oauth2/v3/certs")
if payload == nil then
    print("échec de la récupération de JWKS : ", err)
    return
end
print(payload) -- '{"keys":[{"alg":"RS256","e":"AQAB","kid":"882503a5fd56e9f734dfba5c50d7bf48db284ae9","kty":"RSA","n":"woRUr445_ODXrFeynz5L208aJkABOKQHEzbfGM_V1ijkYZWZKY0PXKPP_wRKcE4C6OyjDNd5gHh3dF5QsVhVDZCfR9QjTf94o4asngrHzdOcfQ0pZIvzu_vzaVG82VGLM-2rKQp8uz06A6TbUzbIv9wQ8wQpYDIdujNkLqL22Mkb2drPxm9Y9I05PmVdkkvAbu4Q_KRJWxykOigHp-hVBmpYS2P3xuX56gM7ZRcXXJKKUfrGel4nDhSIAAD1wBNcVVgKbb0TYfZmVpRSCji_b6JHjqYhYjUasdotYJzWl7quAFsN_X_4j-cHZ30OS81j--OiIxWpL11y1kcbE0u-Dw","use":"sig"},{"n":"m7GlcF1ExRB4braT7sDnZvlY3wpqX9krkVRqcVA-m43FWFYBtuSpd-lc0EV8R8TO180y0tSgJc7hviI1IBJQlNa7XkjVGhY0ZFUp5rTpC45QbA9Smo4CLa5HQIf-69rkkovjFNMuDQvNiYCgRPLyRjmQbN2uHl4fU3hhf5qFqKTKo7eLCZiEMjrOkTXziA7xJJigUGe-ab8U-AXNH1fnCbejzHEIxL0eUG_4r4xddImOxETDO5T65AQCeqs7vtYos2xq5SLFuaUsithRQ-IMm3OlcVhMjBYt6uvGS6IdMjKon4wThCxEqAEXg0nahiGjnQCW176SNF152__TOjQVwQ","alg":"RS256","kty":"RSA","use":"sig","kid":"8e8fc8e556f7a76d08d35829d6f90ae2e12cfd0d","e":"AQAB"}]}'

jwks.verify_jwt_with_jwks

syntax: jwt, err = jwks.verify_jwt_with_jwks(jwt_token, jwks_endpoint, jws_options?)

Étant donné un jwt_token signé sous forme de chaîne, vérifiez sa signature avec JWKS fournies par le service HTTP trouvé à jwks_endpoint.

En cas de succès, le JWT vérifié est retourné sous forme de table lua, sinon nil et une erreur sont retournés.

Le paramètre optionnel jws_options peut être passé pour configurer le validateur de jetons lors de l'appel à jwt.verify après avoir récupéré avec succès le JWKS. Voir la documentation respective de jwt.verify pour plus d'informations sur les options qui peuvent être passées.

local jwks = require("resty.jwt-verification-jwks")

jwt, err = jwks.verify_jwt_with_jwks("<MON_JWT>", "http://myservice:8888/.well-known/jwks.json", nil)
if jwt == nil then
    print("échec de la vérification du jwt : ", err)
    return
end
print(jwt.header.alg)
print(tostring(jwt.payload))

jwks.decrypt_jwt_with_jwks

syntax: jwt, err = jwks.decrypt_jwt_with_jwks(jwt_token, jwks_endpoint, jwe_options?)

Étant donné un jwt_token chiffré sous forme de chaîne, déchiffrez-le avec JWKS fournies par le service HTTP trouvé à jwks_endpoint.

En cas de succès, le JWT déchiffré est retourné sous forme de table lua, sinon nil et une erreur sont retournés.

Le paramètre optionnel jwe_options peut être passé pour configurer le validateur de jetons lors de l'appel à jwt.decrypt après avoir récupéré avec succès le JWKS. Voir la documentation respective de jwt.decrypt pour plus d'informations sur les options qui peuvent être passées.

local jwks = require("resty.jwt-verification-jwks")

jwt, err = jwks.decrypt_jwt_with_jwks("<MON_JWT>", "http://myservice:8888/.well-known/jwks.json", nil)
if jwt == nil then
    print("échec du déchiffrement du jwt : ", err)
    return
end
print(jwt.header.alg)
print(jwt.header.enc)
print(tostring(jwt.payload))

RFCs utilisés comme référence

Exécuter des tests

Configuration

Installer la suite de tests :

sudo cpan Test::Nginx

Installer openresty : voir https://openresty.org/en/linux-packages.html

Exécuter

export PATH=/usr/local/openresty/nginx/sbin:$PATH
prove -r t

Exécuter des benchmarks

La suite de tests Test::Nginx a une intégration de benchmarking intégrée avec weighttp.

Augmenter les limites sysctl

Si vous prévoyez de tester la bibliothèque sous stress, vous devrez peut-être augmenter les limites système.

cat > /etc/sysctl.d/openresty-benchmarks.conf << EOF
net.ipv4.ip_local_port_range=2048 65535

net.ipv4.tcp_tw_reuse=1

net.core.netdev_max_backlog=2000
net.ipv4.tcp_max_syn_backlog=2048
EOF

## appliquer les modifications
sudo sysctl -p /etc/sysctl.d/openresty-benchmarks.conf

Lancer des tests

J'ai créé quelques scénarios pseudo-réalistes dans le dossier benchmarks.

## pour plus d'informations sur la syntaxe : https://openresty.gitbooks.io/programming-openresty/content/testing/test-modes.html
export TEST_NGINX_BENCHMARK='50000 10'
prove -r ./benchmarks

Par défaut, seul 1 travailleur nginx et 1 cœur CPU seront utilisés pour effectuer les benchmarks. Pour augmenter les limites de travailleurs, modifiez la directive workers(1); à l'intérieur des fichiers .t et relancez le benchmark.

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-verification.