Déconnexion dans une application sécurisée OAuth

1. Vue d'ensemble

Dans ce tutoriel rapide, nous allons montrer comment nous pouvons ajouter une fonctionnalité de déconnexion à une application OAuth Spring Security .

Nous verrons plusieurs façons de procéder. Tout d'abord, nous verrons comment déconnecter notre utilisateur Keycloak de l'application OAuth comme décrit dans Création d'une API REST avec OAuth2, puis en utilisant le proxy Zuul que nous avons vu précédemment.

Nous utiliserons la pile OAuth dans Spring Security 5. Si vous souhaitez utiliser la pile héritée Spring Security OAuth, consultez cet article précédent: Déconnexion dans une application sécurisée OAuth (à l'aide de la pile héritée).

2. Déconnexion à l'aide de l'application frontale

Comme les jetons d'accès sont gérés par le serveur d'autorisation, ils devront être invalidés à ce niveau. Les étapes exactes pour ce faire seront légèrement différentes selon le serveur d'autorisation que vous utilisez.

Dans notre exemple, selon la documentation Keycloak, pour se déconnecter directement d'une application de navigateur, nous pouvons rediriger le navigateur vers // auth-server / auth / realms / {realm-name} / protocol / openid-connect / logout? Redirect_uri = encodedRedirectUri .

En plus d'envoyer l'URI de redirection, nous devons également transmettre un id_token_hint au point de terminaison de déconnexion de Keycloak. Cela devrait porter la valeur id_token encodée .

Rappelons comment nous avons enregistré le access_token , nous enregistrerons également le id_token :

saveToken(token) { var expireDate = new Date().getTime() + (1000 * token.expires_in); Cookie.set("access_token", token.access_token, expireDate); Cookie.set("id_token", token.id_token, expireDate); this._router.navigate(['/']); } 

Il est important de noter que pour obtenir le jeton d'ID dans la charge de réponse du serveur d'autorisation, nous devons inclure openid dans le paramètre scope .

Voyons maintenant le processus de déconnexion en action.

Nous modifierons notre fonction de déconnexion dans App Service :

logout() { let token = Cookie.get('id_token'); Cookie.delete('access_token'); Cookie.delete('id_token'); let logoutURL = "//localhost:8083/auth/realms/baeldung/protocol/openid-connect/logout? id_token_hint=" + token + "&post_logout_redirect_uri=" + this.redirectUri; window.location.href = logoutURL; }

Outre la redirection, nous devons également supprimer les jetons d'accès et d'identification que nous avons obtenus du serveur d'autorisation.

Par conséquent, dans le code ci-dessus, nous avons d'abord supprimé les jetons, puis redirigé le navigateur vers l' API de déconnexion de Keycloak .

Notamment, nous avons transmis l'URI de redirection en tant que // localhost: 8089 / - celui que nous utilisons dans toute l'application - nous allons donc nous retrouver sur la page de destination après la déconnexion.

La suppression des jetons d'accès, d'identifiant et d'actualisation correspondant à la session en cours est effectuée à la fin du serveur d'autorisation. Notre application de navigateur n'avait pas du tout enregistré le jeton d'actualisation dans ce cas.

3. Déconnexion à l'aide du proxy Zuul

Dans un article précédent sur la gestion du jeton d'actualisation, nous avons configuré notre application pour pouvoir actualiser le jeton d'accès, à l'aide d'un jeton d'actualisation. Cette implémentation utilise un proxy Zuul avec des filtres personnalisés.

Nous verrons ici comment ajouter la fonctionnalité de déconnexion à ce qui précède.

Cette fois-ci, nous utiliserons une autre API Keycloak pour déconnecter un utilisateur. Nous allons appeler POST sur le point de terminaison de déconnexion pour déconnecter une session via une invocation non-navigateur , au lieu de la redirection d'URL que nous avons utilisée dans la section précédente.

3.1. Définir un itinéraire pour la déconnexion

Pour commencer, ajoutons une autre route au proxy dans notre application.yml :

zuul: routes: //... auth/refresh/revoke: path: /auth/refresh/revoke/** sensitiveHeaders: url: //localhost:8083/auth/realms/baeldung/protocol/openid-connect/logout //auth/refresh route

En fait, nous avons ajouté une sous-route à l' authentification / actualisation déjà existante . Il est important que nous ajoutions la sous-route avant la route principale, sinon Zuul mappera toujours l'URL de la route principale .

Nous avons ajouté une sous-route au lieu d'une route principale afin d'avoir accès au cookie refreshToken uniquement HTTP , qui a été configuré pour avoir un chemin très limité comme / auth / refresh (et ses sous-chemins). Nous verrons pourquoi nous avons besoin du cookie dans la section suivante.

3.2. POST sur le serveur d'autorisation / déconnexion

Maintenant, améliorons l' implémentation de CustomPreZuulFilter pour intercepter l' URL / auth / refresh / revoke et ajouter les informations nécessaires à transmettre au serveur d'autorisation.

Les paramètres de formulaire requis pour la déconnexion sont similaires à ceux de la demande de jeton d'actualisation, sauf qu'il n'y a pas de grant_type :

@Component public class CustomPostZuulFilter extends ZuulFilter { //... @Override public Object run() { //... if (requestURI.contains("auth/refresh/revoke")) { String cookieValue = extractCookie(req, "refreshToken"); String formParams = String.format("client_id=%s&client_secret=%s&refresh_token=%s", CLIENT_ID, CLIENT_SECRET, cookieValue); bytes = formParams.getBytes("UTF-8"); } //... } }

Ici, nous avons simplement extrait le cookie refreshToken et envoyé dans le formulaire formParams requis .

3.3. Supprimer le jeton d'actualisation

Lors de la révocation du jeton d'accès à l'aide de la redirection de déconnexion comme nous l'avons vu précédemment, le jeton d'actualisation qui lui est associé est également invalidé par le serveur d'autorisation.

Cependant, dans ce cas, le cookie httpOnly restera défini sur le client. Étant donné que nous ne pouvons pas le supprimer via JavaScript, nous devons le supprimer du côté serveur.

Pour cela, ajoutons à l' implémentation CustomPostZuulFilter qui intercepte l' URL / auth / refresh / revoke afin qu'elle supprime le cookie refreshToken lors de la rencontre de cette URL:

@Component public class CustomPostZuulFilter extends ZuulFilter { //... @Override public Object run() { //... String requestMethod = ctx.getRequest().getMethod(); if (requestURI.contains("auth/refresh/revoke")) { Cookie cookie = new Cookie("refreshToken", ""); cookie.setMaxAge(0); ctx.getResponse().addCookie(cookie); } //... } }

3.4. Supprimer le jeton d'accès du client angulaire

Outre la révocation du jeton d'actualisation, le cookie access_token devra également être supprimé du côté client.

Ajoutons une méthode à notre contrôleur angulaire qui efface le cookie access_token et appelle le mappage POST / auth / refresh / revoke :

logout() { let headers = new HttpHeaders({ 'Content-type': 'application/x-www-form-urlencoded; charset=utf-8'}); this._http.post('auth/refresh/revoke', {}, { headers: headers }) .subscribe( data => { Cookie.delete('access_token'); window.location.href = '//localhost:8089/'; }, err => alert('Could not logout') ); }

Cette fonction sera appelée en cliquant sur le bouton Déconnexion:

Logout

4. Conclusion

Dans ce tutoriel rapide mais approfondi, nous avons montré comment nous pouvons déconnecter un utilisateur d'une application sécurisée OAuth et invalider les jetons de cet utilisateur.

Le code source complet des exemples est disponible à l'adresse over sur GitHub.