Je travaille sur une application web qui doit gérer de très fortes impulsions d'utilisateurs simultanés, qui doivent être autorisés, à demander un contenu identique. Dans son état actuel, il est totalement paralysant, même pour une instance AWS à 32 cœurs.
(Notez que nous utilisons Nginx comme proxy inverse)
La réponse ne peut pas être simplement mise en cache car, dans le pire des cas, il faut vérifier si l'utilisateur est authentifié en décodant son JWT. Cela nous oblige à lancer Laravel 4, ce que la plupart conviendront, est lent , même avec PHP-FPM et OpCache activés. Cela est principalement dû à la phase de bootstrapping lourde.
On pourrait se poser la question "Pourquoi avez-vous utilisé PHP et Laravel en premier lieu si vous saviez que cela allait être un problème?" - mais il est trop tard maintenant pour revenir sur cette décision!
Solution possible
Une solution qui a été proposée est d'extraire le module Auth de Laravel vers un module externe léger (écrit en quelque chose de rapide comme C) dont la responsabilité est de décoder le JWT et de décider si l'utilisateur est authentifié.
Le flux d'une demande serait:
- Vérifiez si le cache est atteint (sinon passez à PHP comme d'habitude)
- Décoder le jeton
- Vérifiez s'il est valide
- Si valide , servir à partir du cache
- S'il n'est pas valide , dites-le à Nginx, puis Nginx transmettra ensuite la demande à PHP pour traiter normalement.
Cela nous permettra de ne pas frapper PHP une fois que nous aurons servi cette demande à un seul utilisateur et à la place de contacter un module léger pour déconner avec les JWT de décodage et toutes les autres mises en garde fournies avec ce type d'authentification.
Je pensais même à écrire ce code directement en tant que module d'extension HTTP Nginx.
Préoccupations
Ma préoccupation est que je n'ai jamais vu cela se faire auparavant et je me suis demandé s'il y avait une meilleure façon.
De plus, la seconde où vous ajoutez un contenu spécifique à la page, cela tue totalement cette méthode.
Existe-t-il une autre solution plus simple disponible directement dans Nginx? Ou devrions-nous utiliser quelque chose de plus spécialisé comme le vernis?
Mes questions:
La solution ci-dessus est-elle logique?
Comment cela est-il normalement abordé?
Existe-t-il un meilleur moyen d'obtenir un gain de performances similaire ou meilleur?
la source
Réponses:
J'ai essayé de résoudre un problème similaire. Mes utilisateurs doivent être authentifiés pour chaque demande qu'ils font. Je me suis concentré sur l'authentification des utilisateurs au moins une fois par l'application backend (validation du token JWT), mais après cela, j'ai décidé de ne plus avoir besoin du backend.
J'ai choisi d'éviter d'exiger tout plugin Nginx qui n'est pas inclus par défaut. Sinon, vous pouvez vérifier les scripts nginx-jwt ou Lua et ce seraient probablement d'excellentes solutions.
Adressage de l'authentification
Jusqu'à présent, j'ai fait ce qui suit:
Délégué l'authentification à Nginx à l'aide de
auth_request
. Cela appelle uninternal
emplacement qui transmet la demande à mon point de terminaison de validation de jeton d'arrière-plan. Cela ne résout pas à lui seul la question du traitement d'un nombre élevé de validations.Le résultat de la validation du jeton est mis en cache à l'aide d'une
proxy_cache_key "$cookie_token";
directive. Une fois la validation du jeton réussie, le backend ajoute uneCache-Control
directive qui indique à Nginx de ne mettre en cache le jeton que pendant 5 minutes maximum. À ce stade, tout jeton d'authentification validé une fois se trouve dans le cache, les demandes ultérieures du même utilisateur / jeton ne touchent plus le backend d'authentification!Pour protéger mon application backend contre les inondations potentielles par des jetons invalides, je cache également les validations refusées, lorsque mon point de terminaison backend renvoie 401. Celles-ci ne sont mises en cache que pendant une courte durée pour éviter de potentiellement remplir le cache Nginx avec de telles demandes.
J'ai ajouté quelques améliorations supplémentaires telles qu'un point de terminaison de déconnexion qui invalide un jeton en renvoyant 401 (qui est également mis en cache par Nginx) afin que si l'utilisateur clique sur déconnexion, le jeton ne puisse plus être utilisé même s'il n'est pas expiré.
De plus, mon cache Nginx contient pour chaque jeton, l'utilisateur associé en tant qu'objet JSON, ce qui m'évite de le récupérer dans la base de données si j'ai besoin de ces informations; et me sauve également du décryptage du jeton.
À propos de la durée de vie des jetons et des jetons d'actualisation
Après 5 minutes, le jeton aura expiré dans le cache, donc le backend sera à nouveau interrogé. Il s'agit de garantir que vous pouvez invalider un jeton, car l'utilisateur se déconnecte, car il a été compromis, etc. Une telle revalidation périodique, avec une implémentation appropriée dans le backend, m'évite d'avoir à utiliser des jetons de rafraîchissement.
Traditionnellement, les jetons d'actualisation sont utilisés pour demander un nouveau jeton d'accès; ils seraient stockés dans votre backend et vous vérifieriez qu'une demande de jeton d'accès est faite avec un jeton d'actualisation qui correspond à celui que vous avez dans la base de données pour cet utilisateur spécifique. Si l'utilisateur se déconnecte ou si les jetons sont compromis, vous supprimez / invalidez le jeton d'actualisation dans votre base de données afin que la prochaine demande de nouveau jeton utilisant le jeton d'actualisation invalidé échoue.
En bref, les jetons de rafraîchissement ont généralement une longue validité et sont toujours vérifiés par rapport au backend. Ils sont utilisés pour générer des jetons d'accès qui ont une validité très courte (quelques minutes). Ces jetons d'accès atteignent normalement votre backend mais vous ne vérifiez que leur signature et leur date d'expiration.
Ici, dans ma configuration, nous utilisons des jetons avec une validité plus longue (pouvant être des heures ou un jour), qui ont le même rôle et les mêmes fonctionnalités qu'un jeton d'accès et un jeton d'actualisation. Parce que nous avons mis en cache leur validation et invalidation par Nginx, ils ne sont entièrement vérifiés par le backend qu'une fois toutes les 5 minutes. Nous conservons donc l'avantage d'utiliser des jetons d'actualisation (pouvoir invalider rapidement un jeton) sans la complexité supplémentaire. Et la validation simple n'atteint jamais votre backend qui est au moins 1 ordre de grandeur plus lent que le cache Nginx, même s'il n'est utilisé que pour la signature et la vérification de la date d'expiration.
Avec cette configuration, je pouvais désactiver l'authentification dans mon backend, car toutes les demandes entrantes atteignent la
auth_request
directive Nginx avant de la toucher.Cela ne résout pas complètement le problème si vous devez effectuer une autorisation par ressource, mais au moins vous avez enregistré la partie d'autorisation de base. Et vous pouvez même éviter de déchiffrer le jeton ou faire une recherche de base de données pour accéder aux données du jeton, car la réponse d'authentification en cache Nginx peut contenir des données et les transmettre au backend.
Maintenant, ma plus grande préoccupation est que je puisse briser quelque chose d'évident lié à la sécurité sans m'en rendre compte. Cela étant dit, tout jeton reçu est toujours validé au moins une fois avant d'être mis en cache par Nginx. Tout jeton tempéré serait différent et ne toucherait donc pas le cache puisque la clé de cache serait également différente.
En outre, il convient peut-être de mentionner qu'une authentification dans le monde réel lutterait contre le vol de jetons en générant (et en vérifiant) un Nonce supplémentaire ou quelque chose.
Voici un extrait simplifié de ma configuration Nginx pour mon application:
Maintenant, voici l'extrait de configuration pour le
/auth
point de terminaison interne , inclus ci-dessus comme/usr/local/etc/nginx/include-auth-internal.conf
:.
Traitement de la diffusion de contenu
Maintenant, l'authentification est séparée des données. Puisque vous avez dit qu'il était identique pour chaque utilisateur, le contenu lui-même peut également être mis en cache par Nginx (dans mon exemple, dans la
content_cache
zone).Évolutivité
Ce scénario fonctionne très bien en supposant que vous avez un serveur Nginx. Dans un scénario réel, vous avez probablement une haute disponibilité, ce qui signifie plusieurs instances Nginx, potentiellement hébergeant également votre application dorsale (Laravel). Dans ce cas, toute demande de vos utilisateurs pourrait être envoyée à l'un de vos serveurs Nginx, et jusqu'à ce qu'ils aient tous mis en cache localement le jeton, ils continueront d'atteindre votre serveur pour le vérifier. Pour un petit nombre de serveurs, l'utilisation de cette solution apporterait tout de même de gros avantages.
Cependant, il est important de noter qu'avec plusieurs serveurs Nginx (et donc des caches), vous perdez la possibilité de vous déconnecter côté serveur car vous ne pouvez pas purger (en forçant une actualisation) le cache des jetons sur chacun d'eux, comme
/auth/logout
fait dans mon exemple. Il ne vous reste plus que la durée du cache de jetons de 5 minutes qui forcera votre backend à être interrogé bientôt et indiquera à Nginx que la demande est refusée. Une solution de contournement partielle consiste à supprimer l'en-tête de jeton ou le cookie sur le client lors de la déconnexion.Tout commentaire serait le bienvenu et apprécié!
la source