« Comment contrôler OAuth dans une application à équilibrage de charge ? » est typique des questions OAuth 2.0 que nous entendons. La réponse courte est : le clustering de session pour OAuth n’est en aucun cas unique. La réponse plus longue est que la gestion des sessions de cluster est toujours susceptible d’être un problème. Cet article explique comment une connexion OAuth affecte votre session d’application. Pour le démontrer, nous allons créer une application de base, sécurisée et à charge équilibrée.
Table des matières
- Sessions et applications OAuth 2.0
- Apatride avec JWT
- Sessions persistantes de l’équilibreur de charge
- Configurer HAproxy et Redis
- Configurer HAproxy
- Démarrez HAproxy et Redis avec Docker
- Construire une application de démarrage sécurisé Spring
- Démarrer l’application Spring Boot
- Secure Spring Boot avec OAuth 2.0
- Partager des sessions avec Redis
- En savoir plus OAuth 2.0 et gestion de session
Conditions préalables
Sessions et applications OAuth 2.0
Une application qui utilise une redirection OAuth 2.0, un « octroi de code d’autorisation », utilise généralement une session côté serveur pour stocker temporairement l’état du processus de connexion jusqu’à ce qu’il se termine. Examinons un flux de connexion OAuth simplifié :
-
Un utilisateur demande à se connecter – soit en cliquant sur un bouton de connexion, soit automatiquement lorsqu’il demande une page protégée.
-
L’application Web stocke des informations sur la session en cours – des informations sur l’état OAuth et éventuellement un vérificateur de code PKCE et/ou un nonce pour OpenId Connect (OIDC).
-
La réponse est une redirection du navigateur vers le serveur d’autorisation.
-
L’utilisateur interagit avec le serveur d’autorisation pour fournir des informations d’identification et confirmer le consentement.
-
Les problèmes du serveur d’autorisation sont redirigés avec un code vers l’application Web.
-
L’autorisation de l’utilisateur est finalisée sur le backend.
-
L’application Web lit les données OAuth précédemment stockées à partir de la session.
-
Vérification que l’utilisateur a été autorisé avec le serveur d’autorisation.
-
Réponse du serveur d’authentification avec un jeton d’accès OAuth.
-
L’application Web stocke les jetons d’accès dans la session.
-
L’utilisateur est connecté.
Deux requêtes distinctes sont adressées à l’application Web : la demande de connexion initiale et une étape de vérification. Les deux requêtes accèdent aux mêmes informations de session. Pour votre application, cela signifie qu’une fois que vous démarrez la mise à l’échelle, vous devez penser à la gestion des sessions.
Apatride avec JWT
À ce stade, vous pouvez vous demander si vous pouvez tout mettre dans un JWT (JSON Web Token) et rendre ce processus sans état ; supprimant le besoin de tout regroupement de session. Tu pourrait, mais vous devrez utiliser un JWE (JSON Web Encryption) pour vous assurer que le navigateur n’a pas accès à des données sensibles. Il existe de nombreuses raisons de ne pas utiliser les JWT comme jetons de session, mais ces problèmes sortent du cadre de cet article.
Sessions persistantes de l’équilibreur de charge
Une autre option pour éviter la réplication de session ou le clustering consiste à utiliser des « sessions persistantes » (ou « affinité de session »), mais cela crée un environnement fragile. Si un serveur Web tombe en panne ou est arrêté pour une raison quelconque, tous les utilisateurs associés à ce serveur seront essentiellement déconnectés. Les sessions persistantes violent également les principes à 12 facteurs de l’apatridie des processus.
Ne confondez pas la référence de 12 Factor à « processus d’apatridie » avec ce que l’on entend par « apatride » dans la section précédente. On y fait spécifiquement référence à la nécessité d’un relais applicatif sur « l’état » d’un serveur entre des requêtes, par exemple, des données mises en cache en mémoire ou un fichier. Au lieu de cela, un service de support doit être utilisé. Dans le cas du stockage de session, l’exemple de Redis est utilisé, ce que nous allons faire exactement dans la section suivante.
Configurer HAproxy et Redis
Pour créer une application à charge équilibrée, nous avons besoin d’au moins trois éléments : un équilibreur de charge (HAproxy), un stockage de session partagé (Redis) et plusieurs instances d’une application Web (Spring Boot).
Si vous souhaitez passer directement au code, jetez un œil à ce référentiel GitHub.
Créez un nouveau répertoire pour ce projet :
mkdir oauth-sessions
cd oauth-sessions
Configurer HAproxy
HAproxy est utilisé pour répartir les requêtes entre plusieurs backend
applications et créer un fichier de configuration, haproxy.cfg
, qui servira deux applications Web différentes (une sur le port 8081
, et un autre sur 8082
).
global
daemon
maxconn 2000
# send request logs to stdout, to make debugging easier
log stdout format raw local0
defaults
mode http
log global
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend http-in
bind *:8080
default_backend webapps
backend webapps
balance roundrobin
# `host.docker.internal` refers to host that is running Docker Desktop
# On Linux add `--add-host=host.docker.internal:host-gateway` to `docker run` to
# mimic the functionality
server webapp1 host.docker.internal:8081
server webapp2 host.docker.internal:8082
Assurez-vous qu’il y a une nouvelle ligne de fin, ou vous pourriez avoir des difficultés à démarrer HAproxy.
Démarrer HAproxy et Redis avec Docker
J’exécuterai l’exemple d’application Web directement sur mon ordinateur portable, mais HAproxy et Redis peuvent tous deux s’exécuter en tant que conteneurs Docker. Créer un docker-compose.yml
déposer:
version: '3.8'
services:
haproxy:
image: docker.io/haproxy:2.4-alpine
volumes:
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
extra_hosts:
# Docker Desktop uses `host.docker.internal` for the host,
# mimic this for linux installs, requires Docker 20.10+
- host.docker.internal:host-gateway
ports:
- 8080:8080
redis:
# Starts Redis without persistence
image: docker.io/redis:6.2.5-alpine
ports:
- 6379:6379
Démarrez HAproxy et Redis en exécutant :
Vous pouvez arrêter le processus en appuyant sur Ctrl+C ou en exécutant la commande docker-compose down à partir du même répertoire.
Construire une application de démarrage sécurisé Spring
Maintenant que les dépendances du système sont éliminées, passons à la création d’une application Spring Boot.
Créez une nouvelle application Spring Boot en visitant start.spring.io et en sélectionnant le la toile et Octa dépendances ou en exécutant la commande suivante :
https start.spring.io/starter.tgz
bootVersion==2.5.4
dependencies==web,okta
groupId==com.example
artifactId==webapp
name=="Web Application"
description=="Demo Web Application"
packageName==com.example
javaVersion==11
| tar -xzvf -
Pour donner une indication visuelle du serveur qui a traité la demande, créez un contrôleur REST qui affiche le port du serveur dans src/main/java/com/example/Endpoints.java
:
package com.example;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Endpoints {
@GetMapping("https://dzone.com/")
String serverInfo(@Value("${server.port}") int port) {
return "Hello, your server port is: " + port;
}
}
Démarrer l’application Spring Boot
Dans la section précédente, HAproxy était lié au port 8080
, qui est également le port par défaut pour Spring Boot. Démarrer l’application sur le port 8081
à l’aide de:
SERVER_PORT=8081 ./mvnw spring-boot:run
À ce stade, l’application Spring Boot n’a PAS été configurée pour utiliser Redis ou OAuth, mais vous pouvez toujours tester le serveur en saisissant le mot de passe généré automatiquement à partir de la sortie de la console. Cela ressemblera à quelque chose comme ceci :
Using generated security password: 4302a714-580b-4d01-91d9-5d9597ee1bb5
Copiez le mot de passe et faites une demande à l’application Spring Boot :
http :8081/ --auth user:<your-password>
Vous verrez une réponse contenant le port du serveur :
Hello, your server port is: 8081
Génial, cela signifie que l’application Spring Boot est opérationnelle ! Assurez-vous maintenant que vous pouvez accéder au serveur via l’équilibreur de charge sur le port 8080
:
http :8080/ --auth user:<your-password>
Vous devriez voir la même réponse ; si vous voyez un 503 Service Unavailable
, réessayez la demande.
La configuration HAproxy utilisée dans cet article n’a PAS de vérification de l’état activée, elle alternera donc les demandes entre les ports 8081 et 8082 ; ceci est intentionnel pour simplifier la configuration. Jetez un œil à Spring Actuator si vous souhaitez ajouter des vérifications de l’état et d’autres surveillances à votre application.
Arrêtez le serveur Spring Boot en utilisant Ctrl
+C
. Il est temps de sécuriser l’application avec OAuth 2.0.
Démarrage sécurisé du printemps avec OAuth 2.0
Avant de commencer, vous aurez besoin d’un compte développeur Okta gratuit. Installez l’Okta CLI et exécutez okta register
pour ouvrir un nouveau compte. Si vous avez déjà un compte, lancez okta login
. Ensuite, exécutez okta apps create
. Sélectionnez le nom de l’application par défaut ou modifiez-le comme bon vous semble. Choisir la toile et appuyez sur Entrer.
Sélectionner Démarreur de démarrage à ressort Okta. Acceptez les valeurs d’URI de redirection par défaut qui vous sont fournies. Autrement dit, une redirection de connexion de http://localhost:8080/login/oauth2/code/okta
et une redirection de déconnexion de http://localhost:8080
.
Que fait la CLI d’Okta ?
Maintenant que l’application a été configurée pour utiliser OAuth 2.0, démarrez deux instances différentes (ouvrez deux fenêtres de terminal différentes) :
SERVER_PORT=8081 ./mvnw spring-boot:run
Et le second sur le port 8082
:
SERVER_PORT=8082 ./mvnw spring-boot:run
L’accès aux applications via l’équilibreur de charge produira des résultats étranges ; ouvrir une fenêtre privée/incognito pour http://localhost:8080
et essayez de vous connecter. Vous serez redirigé vers Okta où vous pourrez saisir les informations d’identification de votre compte. Cependant, après avoir appuyé sur la S’identifier, vous verrez une page d’erreur :
Repensez au diagramme de séquence au début de cet article et voyez si vous pouvez repérer le problème. La demande de connexion initiale (étape un) s’est produite sur une instance et la dernière s’est produite sur une autre (étape six). La mise à jour de l’application pour utiliser le stockage de session partagé résoudra le problème.
Faisons cela!
Partager des sessions avec Redis
Si vous suivez, vous avez déjà un serveur Redis en cours d’exécution ; nous allons maintenant configurer l’application Spring Boot pour l’utiliser. Heureusement, Spring Session rend le processus indolore.
Ouvrez le pom.xml
et ajoutez ce qui suit à l’intérieur du <dependencies>
bloquer:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
Par défaut, Spring Session configure Redis pour se connecter à localhost sur le port 6379 et AUCUN mot de passe. Consultez la documentation Spring Session pour connaître les différentes options de configuration.
Redémarrez les applications Spring Boot. (Rappelez-vous qu’il y en a un sur le port 8081
et un autre sur 8082
).
Ouvrez à nouveau votre navigateur et essayez d’accéder http://localhost:8080/
; cette fois, vous pourrez vous connecter sans problème !
Actualisez le navigateur plusieurs fois et vous verrez la réponse alterner entre les ports :
Hello, your server port is: 8081
Et:
Hello, your server port is: 8082
Assez facile, juste quelques dépendances pour configurer le stockage de session partagé ! Si Redis n’est pas votre confiture, Spring Session prend également en charge les bases de données, Hazelcast, MongoDB et Apache Geode.
En savoir plus OAuth 2.0 et gestion de session
Cet article montre comment gérer les sessions pour une application simple à charge équilibrée qui utilise OAuth 2.0. Notre exemple s’est concentré sur la partie Spring Boot du…