openidc: Implémentation de OpenID Connect Relying Party et OAuth 2.0 Resource Server en Lua pour NGINX / nginx-module-lua
Installation
Si vous n'avez pas encore 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-openidc
CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-openidc
Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.
Ce document décrit lua-resty-openidc v1.8.0 publié le 13 septembre 2024.
lua-resty-openidc
lua-resty-openidc est une bibliothèque pour NGINX implémentant la fonctionnalité Relying Party (RP) de OpenID Connect et/ou la fonctionnalité Resource Server (RS) de OAuth 2.0.
Lorsqu'elle est utilisée comme Relying Party OpenID Connect, elle authentifie les utilisateurs auprès d'un fournisseur OpenID Connect en utilisant OpenID Connect Discovery et le profil client de base (c'est-à-dire le flux de code d'autorisation). Lorsqu'elle est utilisée comme Resource Server OAuth 2.0, elle peut valider les jetons d'accès Bearer OAuth 2.0 auprès d'un serveur d'autorisation ou, dans le cas où un JSON Web Token est utilisé comme jeton d'accès, la vérification peut se faire contre un secret/clée préconfiguré.
Elle maintient des sessions pour les utilisateurs authentifiés en s'appuyant sur lua-resty-session, offrant ainsi un choix configurable entre le stockage de l'état de session dans un cookie de navigateur côté client ou l'utilisation des mécanismes de stockage côté serveur shared-memory|memcache|redis.
Elle prend en charge la mise en cache à l'échelle du serveur des documents de découverte résolus et des jetons d'accès validés.
Elle peut être utilisée comme un proxy inverse terminant OAuth/OpenID Connect devant un serveur d'origine afin que le serveur d'origine/services puissent être protégés par les normes pertinentes sans les mettre en œuvre sur le serveur lui-même.
Configuration d'exemple pour la connexion Google+
Configuration d'exemple nginx.conf pour authentifier les utilisateurs via la connexion Google+, protégeant un chemin de proxy inverse.
events {
worker_connections 128;
}
http {
resolver 8.8.8.8;
lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
lua_ssl_verify_depth 5;
# cache pour les documents de métadonnées de découverte
lua_shared_dict discovery 1m;
# cache pour les JWKs
lua_shared_dict jwks 1m;
# NB : si vous avez "lua_code_cache off;", utilisez :
# set $session_secret xxxxxxxxxxxxxxxxxxx;
# voir : https://github.com/bungle/lua-resty-session#notes-about-turning-lua-code-cache-off
server {
listen 8080;
location / {
access_by_lua_block {
local opts = {
-- l'URI de redirection complète doit être protégée par ce script
-- si l'URI commence par un / l'URI de redirection complète devient
-- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri
-- à moins que le schéma ne soit remplacé en utilisant opts.redirect_uri_scheme ou un en-tête X-Forwarded-Proto dans la requête entrante
redirect_uri = "https://MY_HOST_NAME/redirect_uri",
-- jusqu'à la version 1.6.1, vous deviez spécifier
-- redirect_uri_path = "/redirect_uri",
-- et ne pouviez pas définir le nom d'hôte
-- Le point de terminaison de découverte de l'OP. Activez-le pour obtenir l'URI de tous les points de terminaison (Token, introspection, déconnexion...)
discovery = "https://accounts.google.com/.well-known/openid-configuration",
-- L'accès au point de terminaison Token de l'OP nécessite une authentification. Plusieurs modes d'authentification sont pris en charge :
--token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"|"private_key_jwt"|"client_secret_jwt"],
-- o Si token_endpoint_auth_method est défini sur "client_secret_basic", "client_secret_post" ou "client_secret_jwt", l'authentification au point de terminaison Token utilise client_id et client_secret
-- Pour les OP non conformes à OAuth 2.0 RFC 6749 pour l'authentification du client (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1)
-- client_id et client_secret DOIVENT être invariants lors de l'encodage d'URL
client_id = "<client_id>",
client_secret = "<client_secret>",
-- o Si token_endpoint_auth_method est défini sur "private_key_jwt", l'authentification au point de terminaison Token utilise client_id, client_rsa_private_key et client_rsa_private_key_id pour calculer un JWT signé
-- client_rsa_private_key est la clé privée RSA à utiliser pour signer le JWT généré par lua-resty-openidc pour l'authentification auprès de l'OP
-- client_rsa_private_key_id (optionnel) est l'identifiant de clé à définir dans l'en-tête JWT pour identifier quelle clé publique l'OP doit utiliser pour vérifier la signature JWT
--client_id = "<client_id>",
--client_rsa_private_key=[[-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAiThmpvXBYdur716D2q7fYKirKxzZIU5QrkBGDvUOwg5izcTv
[...]
h2JHukolz9xf6qN61QMLSd83+kwoBr2drp6xg3eGDLIkQCQLrkY=
-----END RSA PRIVATE KEY-----]],
--client_rsa_private_key_id="key id#1",
-- Durée de vie exprimée en secondes du JWT signé généré par lua-resty-openidc pour l'authentification auprès de l'OP.
-- (utilisé lorsque token_endpoint_auth_method est défini sur "private_key_jwt" ou "client_secret_jwt"). La valeur par défaut est 60 secondes.
--client_jwt_assertion_expires_in = 60,
-- Lors de l'utilisation de https pour n'importe quel point de terminaison OP, l'application de la vérification du certificat SSL peut être exigée ("yes") ou non ("no").
--ssl_verify = "no",
-- La connexion keepalive avec l'OP peut être activée ("yes") ou désactivée ("no").
--keepalive = "no",
--response_mode=form_post peut être utilisé pour faire en sorte que lua-resty-openidc utilise le [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html). *Remarque* pour les navigateurs modernes, vous devrez définir [`$session_cookie_samesite`](https://github.com/bungle/lua-resty-session#string-sessioncookiesamesite) sur `None` avec form_post à moins que votre fournisseur OpenID Connect et votre Relying Party ne partagent le même domaine.
--authorization_params = { hd="zmartzone.eu" },
--scope = "openid email profile",
-- Rafraîchir l'id_token de l'utilisateur après 900 secondes sans nécessiter de ré-authentification
--refresh_session_interval = 900,
--iat_slack = 600,
--redirect_uri_scheme = "https",
--logout_path = "/logout",
--redirect_after_logout_uri = "/",
-- Où l'utilisateur doit-il être redirigé après la déconnexion de la RP. Cette option remplace tout end_session_endpoint que l'OP aurait pu fournir dans la réponse de découverte.
--redirect_after_logout_with_id_token_hint = true,
-- Si la redirection après la déconnexion doit inclure le jeton id comme indice (si disponible). Cette option n'est utilisée que si redirect_after_logout_uri est défini.
--post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
-- Où la RP demande que l'OP redirige l'utilisateur après la déconnexion. Si cette option est définie sur une URI relative, elle sera relative au point de terminaison de déconnexion de l'OP, et non à celui de la RP.
--accept_none_alg = false
-- si votre fournisseur OpenID Connect ne signe pas ses jetons id
-- (utilise l'algorithme de signature "none"), alors définissez ceci sur true.
--accept_unsupported_alg = true
-- si vous souhaitez rejeter les jetons signés à l'aide d'un algorithme
-- non pris en charge par lua-resty-jwt, définissez ceci sur false. Si
-- vous ne le définissez pas ou le définissez sur true, la signature du jeton ne sera pas
-- vérifiée lorsque un algorithme non pris en charge est utilisé.
--renew_access_token_on_expiry = true
-- si ce plugin doit essayer de renouveler silencieusement le jeton d'accès une fois qu'il est expiré si un jeton de rafraîchissement est disponible.
-- si le renouvellement du jeton échoue, l'utilisateur sera redirigé vers le point de terminaison d'autorisation.
--access_token_expires_in = 3600
-- Durée de vie par défaut en secondes du access_token si aucun attribut expires_in n'est présent dans la réponse du point de terminaison du jeton.
--access_token_expires_leeway = 0
-- Marge d'expiration pour le renouvellement du access_token. Si cela est défini, le renouvellement se produira access_token_expires_leeway secondes avant l'expiration du jeton. Cela évite les erreurs dans le cas où le access_token expire juste à l'arrivée au serveur de ressources OAuth.
--force_reauthorize = false
-- Lorsque force_reauthorize est défini sur true, le flux d'autorisation sera exécuté même si un jeton a déjà été mis en cache
--session_contents = {id_token=true}
-- Liste blanche du contenu de session à activer. Cela peut être utilisé pour réduire la taille de la session.
-- Lorsqu'il n'est pas défini, tout sera inclus dans la session.
-- Disponibles :
-- id_token, enc_id_token, user, access_token (inclut le jeton de rafraîchissement)
-- Vous pouvez spécifier des délais d'attente pour connect/send/read comme un seul nombre (définissant tous les délais d'attente) ou comme une table. Les valeurs sont en millisecondes
-- timeout = 1000
-- timeout = { connect = 500, send = 1000, read = 1000 }
--use_nonce = false
-- Par défaut, la requête d'autorisation inclut le
-- paramètre nonce. Vous pouvez utiliser cette option pour le désactiver
-- ce qui peut être nécessaire lorsque vous parlez à un fournisseur OpenID
-- Connect défectueux qui ignore le paramètre car le
-- id_token sera rejeté sinon.
--revoke_tokens_on_logout = false
-- Lorsque revoke_tokens_on_logout est défini sur true, une déconnexion notifie le serveur d'autorisation que les jetons de rafraîchissement et d'accès précédemment obtenus ne sont plus nécessaires. Cela nécessite que revocation_endpoint soit découvrable.
-- S'il n'y a pas de point de terminaison de révocation fourni ou s'il y a des erreurs lors de la révocation, l'utilisateur ne sera pas notifié et le processus de déconnexion se poursuivra normalement.
-- Optionnel : utiliser un proxy sortant vers les points de terminaison du fournisseur OpenID Connect avec la table proxy_opts :
-- cela nécessite lua-resty-http >= 0.12
-- proxy_opts = {
-- http_proxy = "http://<proxy_host>:<proxy_port>/",
-- https_proxy = "http://<proxy_host>:<proxy_port>/"
-- }
-- Hooks de cycle de vie
--
-- lifecycle = {
-- on_created = handle_created,
-- on_authenticated = handle_authenticated,
-- on_regenerated = handle_regenerated
-- on_logout = handle_logout
-- }
--
-- où `handle_created`, `handle_authenticated`, `handle_regenerated` et `handle_logout` sont des appelables
-- acceptant l'argument `session`. `handle_created` accepte également un deuxième argument `params` qui est une table
-- contenant les paramètres de requête de la demande d'autorisation utilisée pour rediriger l'utilisateur vers le fournisseur OpenID
-- Connect.
--
-- -- Le hook `on_created` est invoqué *après* qu'une session a été créée dans
-- `openidc_authorize` immédiatement avant de sauvegarder la session
-- -- Le hook `on_authenticated` est invoqué *après* avoir reçu la réponse d'autorisation dans
-- `openidc_authorization_response` immédiatement avant de sauvegarder la session
-- À partir de lua-resty-openidc 1.7.5, cela reçoit le id_token décodé comme deuxième et la réponse du point de terminaison du jeton comme troisième argument
-- -- `on_regenerated` est invoqué immédiatement après que
un nouveau jeton d'accès a été obtenu via le rafraîchissement du jeton
et est appelé avec la table de session régénérée
-- -- Le hook `on_logout` est invoqué *avant* qu'une session soit détruite dans
-- `openidc_logout`
--
-- Tous, certains ou aucun des hooks peuvent être utilisés. Un `lifecycle` vide ne fait rien.
-- Un hook qui retourne une valeur vraie provoque l'échec de l'action de cycle de vie dont ils font partie.
-- Optionnel : ajouter un décorateur pour la requête HTTP qui est
-- appliqué lorsque lua-resty-openidc communique directement avec le fournisseur OpenID Connect.
-- Peut être utilisé pour fournir des en-têtes HTTP supplémentaires
-- ou ajouter un autre comportement similaire.
-- http_request_decorator = function(req)
-- local h = req.headers or {}
-- h[EXTRA_HEADER] = 'my extra header'
-- req.headers = h
-- return req
-- end,
-- use_pkce = false,
-- lorsqu'il est défini sur true, la "Proof Key for Code Exchange" telle que
-- définie dans la RFC 7636 sera utilisée. La méthode de challenge de code
-- sera toujours S256
}
-- appeler authenticate pour l'authentification des utilisateurs OpenID Connect
local res, err = require("resty.openidc").authenticate(opts)
if err then
ngx.status = 500
ngx.say(err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- à ce stade, res est une table Lua avec 3 clés :
-- id_token : une table Lua avec les claims du id_token (requis)
-- access_token: le jeton d'accès (optionnel)
-- user : une table Lua avec les claims retournés par le point de terminaison d'informations utilisateur (optionnel)
--if res.id_token.hd ~= "zmartzone.eu" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
--if res.user.email ~= "[email protected]" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
-- définir les en-têtes avec les informations utilisateur : cela écrasera tous les en-têtes existants
-- mais les nettoiera également (!) au cas où aucune valeur ne serait fournie dans le jeton
ngx.req.set_header("X-USER", res.id_token.sub)
}
proxy_pass http://localhost:80;
}
}
}
À propos de redirect_uri
Le soi-disant redirect_uri est un URI qui fait partie du protocole OpenID Connect. L'URI de redirection est enregistré auprès de votre fournisseur OpenID Connect et est l'URI vers lequel votre fournisseur redirigera les utilisateurs après une connexion réussie. Cet URI est ensuite géré par lua-resty-openidc où il obtient des jetons et effectue certaines vérifications et seulement après cela, le navigateur est redirigé vers l'endroit où votre utilisateur voulait aller initialement.
Le redirect_uri n'est pas censé être géré par votre code d'application. Il doit être un URI dont lua-resty-openidc est responsable, donc il doit être dans une location protégée par lua-resty-openidc.
Vous configurez le redirect_uri du côté de lua-resty-openidc via le paramètre opts.redirect_uri (qui par défaut est /redirect_uri). S'il commence par un /, alors lua-resty-openidc préfixera le protocole et le nom d'hôte actuel lorsqu'il enverra l'URI au fournisseur OpenID Connect (en tenant compte des en-têtes HTTP Forwarded et X-Forwarded-*). Mais vous pouvez également spécifier un URI absolu contenant l'hôte et le protocole vous-même.
Avant la version 1.6.1, opts.redirect_uri_path était la manière de configurer le redirect_uri sans option pour contrôler les parties de protocole et d'hôte.
Chaque fois que lua-resty-openidc "voit" un chemin local navigué qui correspond au chemin de opts.redirect_uri (ou opts.redirect_uri_path), il interceptera la requête et la gérera lui-même.
Cela fonctionne pour la plupart des cas, mais parfois l'URI de redirection visible de l'extérieur a un chemin différent de celui qui est localement visible pour le serveur. Cela peut se produire si un proxy inverse devant votre serveur réécrit les URIs avant de transmettre les requêtes. Par conséquent, la version 1.7.6 a introduit une nouvelle option opts.local_redirect_uri_path. Si elle est définie, lua-resty-openidc interceptera les requêtes vers ce chemin plutôt que le chemin de opts.redirect_uri.
Vérifier uniquement l'authentification
-- vérifier la session, mais ne pas rediriger vers l'auth si pas déjà connecté
local res, err = require("resty.openidc").authenticate(opts, nil, "pass")
Vérifier uniquement l'authentification et refuser l'accès non authentifié
-- vérifier la session, ne pas rediriger vers l'auth si pas déjà connecté mais retourner une erreur à la place
local res, err = require("resty.openidc").authenticate(opts, nil, "deny")
Sessions et verrouillage
La fonction authenticate retourne l'objet de session actuel comme son quatrième argument de retour. Si vous avez configuré lua-resty-session pour utiliser un backend de stockage côté serveur qui utilise le verrouillage, la session peut encore être verrouillée lorsqu'elle est retournée. Dans ce cas, vous voudrez peut-être la fermer explicitement.
local res, err, target, session = require("resty.openidc").authenticate(opts)
session:close()
Mise en cache
lua-resty-openidc peut utiliser des caches en mémoire partagée pour plusieurs choses. Si vous souhaitez qu'il utilise les caches, vous devez utiliser lua_shared_dict dans votre fichier nginx.conf.
Actuellement, jusqu'à quatre caches sont utilisés
- le cache nommé
discoverystocke les métadonnées de découverte OpenID Connect de votre fournisseur OpenID Connect. Les éléments du cache expirent après 24 heures, sauf si remplacés paropts.discovery_expires_in(une valeur donnée en secondes). Ce cache stockera un élément par URI d'émetteur et vous pouvez consulter le document de découverte vous-même pour obtenir une estimation de la taille requise - généralement quelques kB par fournisseur OpenID Connect. - le cache nommé
jwksstocke le matériel de clé de votre fournisseur OpenID Connect s'il est fourni via le point de terminaison JWKS. Les éléments du cache expirent après 24 heures, sauf si remplacés paropts.jwks_expires_in. Ce cache stockera un élément par URI JWKS et vous pouvez consulter les jwks vous-même pour obtenir une estimation de la taille requise - généralement quelques kB par fournisseur OpenID Connect. - le cache nommé
introspectionstocke le résultat de l'introspection du jeton OAuth2. Les éléments du cache expirent lorsque le jeton correspondant expire. Les jetons avec une expiration inconnue ne sont pas du tout mis en cache. Ce cache contiendra une entrée par jeton d'accès introspecté - généralement, cela sera quelques kB par jeton. - le cache nommé
jwt_verificationstocke le résultat de la vérification JWT. Les éléments du cache expirent lorsque le jeton correspondant expire. Les jetons avec une expiration inconnue ne sont pas mis en cache pendant deux minutes. Ce cache contiendra une entrée par JWT vérifié - généralement, cela sera quelques kB par jeton.
Mise en cache des résultats d'introspection et de vérification JWT
Notez que les caches jwt_verification et introspection sont partagés entre toutes les locations configurées. Si vous utilisez des locations avec une configuration opts différente, le cache partagé peut permettre à un jeton qui est valide pour une seule location d'être accepté par une autre s'il est lu à partir du cache. Afin d'éviter toute confusion de cache, il est recommandé de définir opts.cache_segment sur des chaînes uniques pour chaque ensemble de locations connexes.
Révoquer des jetons
La fonction revoke_tokens(opts, session) révoque le jeton de rafraîchissement et le jeton d'accès actuels. Contrairement à une déconnexion complète, le cookie de session ne sera pas détruit et le point de terminaison de fin de session ne sera pas appelé. La fonction retourne true si les deux jetons ont été révoqués avec succès. Cette fonction peut être utile dans des scénarios où vous souhaitez détruire/retirer une session du côté serveur.
Avec revoke_token(opts, token_type_hint, token), il est également possible de révoquer un jeton spécifique. token_type_hint peut généralement être refresh_token ou access_token.
Configuration d'exemple pour la validation des jetons JWT OAuth 2.0
Configuration d'exemple nginx.conf pour vérifier les jetons d'accès Bearer JWT contre un secret/clée préconfiguré. Une fois vérifié avec succès, le serveur NGINX peut fonctionner comme un proxy inverse vers un serveur d'origine interne.
events {
worker_connections 128;
}
http {
resolver 8.8.8.8;
# cache pour les résultats de vérification JWT
lua_shared_dict jwt_verification 10m;
server {
listen 8080;
location /api {
access_by_lua '
local opts = {
-- 1. exemple d'un secret partagé pour la vérification de signature HS???
--symmetric_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
-- dans les versions jusqu'à 1.6.1, la clé de cette option aurait été secret
-- plutôt que symmetric_key
-- 2. un autre exemple d'un certificat public pour la vérification de signature RS???
public_key = [[-----BEGIN CERTIFICATE-----
MIIC0DCCAbigAwIBAgIGAVSbMZs1MA0GCSqGSIb3DQEBCwUAMCkxCzAJBgNVBAYTAlVTMQwwCgYD
VQQKEwNibGExDDAKBgNVBAMTA2JsYTAeFw0xNjA1MTAxNTAzMjBaFw0yNjA1MDgxNTAzMjBaMCkx
CzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNibGExDDAKBgNVBAMTA2JsYTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAIcLtHjX2GFxYv1033dvfohyCU6nsuR1qoDXfHTG3Mf/Yj4BfLHtMjJr
nR3sgHItH3B6qZPnfErfsN0LP4uZ10/74CrWVqT5dy6ecXMqYtz/KNJ8rG0vY8vltc417AU4fie8
gyeWv/Z6wHWUCf3NHRV8GfFgfuvywgUpHo8ujpUPFr+zrPr8butrzJPq1h3+r0f5P45tfWOdpjCT
gsTzK6urUG0k3WkwdDYapL3wRCAw597nYfgKzzXuh9N0ZL3Uj+eJ6BgCzUZDLXABpMBZfk6hmmzp
cAFV4nTf1AaAs/EOwVE0YgZBJiBrueMcteAIxKrKjEHgThU2Zs9gN9cSFicCAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEAQLU1A58TrSwrEccCIy0wxiGdCwQbaNMohzirc41zRMCXleJXbtsn1vv85J6A
RmejeH5f/JbDqRRRArGMdLooGbqjWG/lwZT456Q6DXqF2plkBvh37kp/GjthGyR8ODJn5ekZwxuB
OcTuruRhqYOIJjiYZSgK/P0zUw1cjLwUJ9ig/O6ozYmof83974fygA/wK3SgFNEoFlTkTpOvZhVW
9kLfCVA/CRBfJNKnz5PWBBxd/3XSEuP/fcWqKGTy7zZso4MTB0NKgWO4duGTgMyZbM4onJPyA0CY
lAc5Csj0o5Q+oEhPUAVBIF07m4rd0OvAVPOCQ2NJhQSL1oWASbf+fg==
-----END CERTIFICATE-----]],
-- dans les versions jusqu'à 1.6.1, la clé de cette option aurait été secret
-- plutôt que public_key
-- 3. alternativement, on peut pointer vers un document de découverte qui
-- contient l'entrée "jwks_uri" ; le point de terminaison jwks doit fournir soit une entrée "x5c"
-- soit à la fois les entrées de module "n" et d'exposant "e" pour la vérification de signature RSA
-- discovery = "https://accounts.google.com/.well-known/openid-configuration",
-- l'algorithme de signature que vous vous attendez à ce qu'il ait été utilisé ;
-- peut être une seule chaîne ou une table.
-- Vous devriez définir cela pour des raisons de sécurité afin d'éviter d'accepter un jeton prétendant être signé par HMAC
-- utilisant une clé publique RSA.
--token_signing_alg_values_expected = { "RS256" }
-- si vous souhaitez accepter des jetons non signés (utilisant l'algorithme
-- de signature "none"), alors définissez ceci sur true.
--accept_none_alg = false
-- si vous souhaitez rejeter les jetons signés à l'aide d'un algorithme
-- non pris en charge par lua-resty-jwt, définissez ceci sur false. Si
-- vous ne le définissez pas, la signature du jeton ne sera pas
-- vérifiée du tout.
--accept_unsupported_alg = true
-- le temps d'expiration en secondes pour le cache jwk, la valeur par défaut est 1 jour.
--jwk_expires_in = 24 * 60 * 60
-- Il peut être nécessaire de forcer la vérification pour un jeton bearer et d'ignorer les résultats de vérification
-- en cache existants. Si c'est le cas, vous devez définir l'option jwt_verification_cache_ignore sur true.
-- jwt_verification_cache_ignore = true
-- nom optionnel d'un segment de cache si vous avez besoin de caches séparés
-- pour des emplacements configurés différemment
-- cache_segment = 'api'
}
-- appeler bearer_jwt_verify pour la validation des JWT OAuth 2.0
local res, err = require("resty.openidc").bearer_jwt_verify(opts)
if err or not res then
ngx.status = 403
ngx.say(err and err or "no access_token provided")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- à ce stade, res est une table Lua qui représente le JSON (validé)
-- charge utile dans le jeton JWT ; maintenant nous ne voulons généralement pas autoriser n'importe quel
-- jeton qui a été émis par le serveur d'autorisation mais nous voulons appliquer
-- certaines restrictions d'accès via des IDs clients ou des scopes
--if res.scope ~= "edit" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
--if res.client_id ~= "ro_client" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
';
proxy_pass http://localhost:80;
}
}
}
Configuration d'exemple pour PingFederate OAuth 2.0
Configuration d'exemple nginx.conf pour valider les jetons d'accès Bearer contre un serveur d'autorisation PingFederate OAuth 2.0.
events {
worker_connections 128;
}
http {
resolver 8.8.8.8;
lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
lua_ssl_verify_depth 5;
# cache pour les résultats de validation
lua_shared_dict introspection 10m;
server {
listen 8080;
location /api {
access_by_lua '
local opts = {
introspection_endpoint="https://localhost:9031/as/introspect.oauth2",
client_id="rs_client",
client_secret="2Federate",
ssl_verify = "no",
-- Par défaut à "exp" - Contrôle le TTL du cache d'introspection
-- https://tools.ietf.org/html/rfc7662#section-2.2
-- introspection_expiry_claim = "exp"
-- nom optionnel d'un segment de cache si vous avez besoin de caches séparés
-- pour des emplacements configurés différemment
-- cache_segment = 'api'
}
-- appeler introspect pour la validation du jeton d'accès Bearer OAuth 2.0
local res, err = require("resty.openidc").introspect(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- à ce stade, res est une table Lua qui représente l'objet JSON
-- retourné par le point de terminaison d'introspection/validation
--if res.scope ~= "edit" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
--if res.client_id ~= "ro_client" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
';
}
}
}
Configuration d'exemple pour passer des jetons d'accès OAuth 2.0 Bearer en tant que cookie
Configuration d'exemple nginx.conf pour valider les jetons d'accès Bearer passés en tant que cookie contre un serveur d'autorisation ORY/Hydra.
events {
worker_connections 128;
}
http {
resolver 8.8.8.8;
lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
lua_ssl_verify_depth 5;
# cache pour les résultats de validation
lua_shared_dict introspection 10m;
server {
listen 8080;
location /api {
access_by_lua '
local opts = {
-- définit l'URI du point de terminaison d'introspection
introspection_endpoint="https://localhost:9031/oauth2/introspect",
-- alternativement, si votre fournisseur OAuth2 fournit un document de découverte contenant le
-- claim introspection_endpoint, vous pouvez laisser l'option introspection_endpoint
-- non définie et utiliser à la place
-- discovery = "https://my-oauth2-provider/.well-known/oauth-authorization-server",
client_id="admin",
client_secret="demo-password",
ssl_verify = "no",
-- Définit l'intervalle en secondes après lequel un jeton d'accès mis en cache et introspecté doit
-- être rafraîchi en l'introspectant (et en le validant) à nouveau contre le serveur d'autorisation.
-- Lorsqu'il n'est pas défini, la valeur est 0, ce qui signifie qu'il expire uniquement après le `exp` (ou alternatif,
-- voir introspection_expiry_claim) indice tel que retourné par le serveur d'autorisation
-- introspection_interval = 60,
-- Définit la manière dont les jetons d'accès OAuth 2.0 Bearer peuvent être passés à ce serveur de ressources.
-- "cookie" en tant qu'en-tête de cookie appelé "PA.global" ou en utilisant le nom spécifié après ":"
-- "header" "Authorization: bearer" en-tête
-- Lorsqu'il n'est pas défini, l'en-tête par défaut "Authorization: bearer" est utilisé
-- auth_accept_token_as = "cookie:PA",
-- Si l'en-tête est utilisé, le champ d'en-tête est Authorization
-- auth_accept_token_as_header_name = "cf-Access-Jwt-Assertion"
-- Méthode d'authentification pour le point de terminaison d'introspection du serveur d'autorisation OAuth 2.0,
-- Utilisé pour authentifier le client auprès du point de terminaison d'introspection avec un client_id/client_secret
-- Par défaut à "client_secret_post"
-- introspection_endpoint_auth_method = "client_secret_basic",
-- Spécifiez les noms des cookies séparés par des espaces à récupérer depuis le navigateur et à envoyer lors des
-- appels backchannel aux points de terminaison OP et AS.
-- Lorsqu'il n'est pas défini, aucun cookie de ce type n'est envoyé.
-- pass_cookies = "JSESSION"
-- Par défaut à "exp" - Contrôle le TTL du cache d'introspection
-- https://tools.ietf.org/html/rfc7662#section-2.2
-- introspection_expiry_claim = "exp"
-- Il peut être nécessaire de forcer un appel d'introspection pour un access_token et d'ignorer les résultats d'introspection
-- en cache existants. Si c'est le cas, vous devez définir l'option introspection_cache_ignore sur true.
-- introspection_cache_ignore = true
-- nom optionnel d'un segment de cache si vous avez besoin de caches séparés
-- pour des emplacements configurés différemment
-- cache_segment = 'api'
}
-- appeler introspect pour la validation du jeton d'accès Bearer OAuth 2.0
local res, err = require("resty.openidc").introspect(opts)
if err then
ngx.status = 403
ngx.say(err)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- à ce stade, res est une table Lua qui représente l'objet JSON
-- retourné par le point de terminaison d'introspection/validation
--if res.scope ~= "edit" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
--if res.client_id ~= "ro_client" then
-- ngx.exit(ngx.HTTP_FORBIDDEN)
--end
';
}
}
}
Journalisation
La journalisation peut être personnalisée, y compris l'utilisation d'un journaliseur personnalisé et le remappage des niveaux de journalisation par défaut d'OpenIDC, par exemple :
local openidc = require("resty.openidc")
openidc.set_logging(nil, { DEBUG = ngx.INFO })
Exécution des tests
Nous avons créé une configuration dockerisée pour le test afin de simplifier l'installation des dépendances.
Pour exécuter les tests, effectuez
$ docker build -f tests/Dockerfile . -t lua-resty-openidc/test
$ docker run -it --rm lua-resty-openidc/test:latest
si vous souhaitez créer luacov couverture lors des tests, utilisez
$ docker run -it --rm -e coverage=t lua-resty-openidc/test:latest
comme deuxième commande
Support
Pour des questions générales, consultez les pages Wiki avec les questions fréquemment posées à :
https://github.com/zmartzone/lua-resty-openidc/wiki
Toute question/problème doit être adressé au suivi des discussions ou des problèmes de Github.
Avertissement
Ce logiciel est open source par ZmartZone IAM mais n'est pas supporté commercialement en tant que tel. Toute question/problème doit être adressé au suivi des discussions ou des problèmes de Github. Voir également le fichier DISCLAIMER dans ce répertoire.
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-openidc.
