Esci in un'applicazione protetta OAuth

1. Panoramica

In questo breve tutorial, mostreremo come aggiungere funzionalità di logout a un'applicazione OAuth Spring Security .

Vedremo un paio di modi per farlo. Per prima cosa, vedremo come disconnettere il nostro utente Keycloak dall'applicazione OAuth come descritto in Creazione di un'API REST con OAuth2, quindi, utilizzando il proxy Zuul che abbiamo visto in precedenza.

Useremo lo stack OAuth in Spring Security 5. Se desideri utilizzare lo stack legacy OAuth di Spring Security, dai un'occhiata a questo articolo precedente: Disconnettersi in un'applicazione protetta OAuth (utilizzando lo stack legacy).

2. Disconnettersi utilizzando l'applicazione front-end

Poiché i token di accesso sono gestiti dal server di autorizzazione, dovranno essere invalidati a questo livello. I passaggi esatti per farlo saranno leggermente diversi a seconda del server di autorizzazione che stai utilizzando.

Nel nostro esempio, come da documentazione di Keycloak, per disconnettersi direttamente da un'applicazione browser, possiamo reindirizzare il browser a // auth-server / auth / realms / {realm-name} / protocol / openid-connect / logout? Redirect_uri = encodedRedirectUri .

Oltre a inviare l'URI di reindirizzamento, dobbiamo anche passare un id_token_hint all'endpoint di disconnessione di Keycloak. Questo dovrebbe contenere il valore id_token codificato .

Ricordiamo come abbiamo salvato access_token , allo stesso modo salveremo anche 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(['/']); } 

È importante sottolineare che per ottenere il token ID nel payload della risposta del server di autorizzazione, dovremmo includere openid nel parametro scope .

Ora vediamo il processo di disconnessione in azione.

Modificheremo la nostra funzione di logout nel servizio app :

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; }

Oltre al reindirizzamento, dobbiamo anche scartare i token di accesso e ID che abbiamo ottenuto dal server di autorizzazione.

Quindi, nel codice precedente, prima abbiamo eliminato i token e quindi reindirizzato il browser all'API di disconnessione di Keycloak .

In particolare, abbiamo passato l'URI di reindirizzamento come // localhost: 8089 / - quello che stiamo usando in tutta l'applicazione - quindi finiremo sulla pagina di destinazione dopo esserci disconnesso.

La cancellazione dei token di accesso, ID e aggiornamento corrispondenti alla sessione corrente viene eseguita alla fine del server di autorizzazione. In questo caso, la nostra applicazione browser non aveva salvato affatto il token di aggiornamento.

3. Disconnettersi utilizzando Zuul Proxy

In un precedente articolo sulla gestione del token di aggiornamento, abbiamo configurato la nostra applicazione per poter aggiornare il token di accesso, utilizzando un token di aggiornamento. Questa implementazione utilizza un proxy Zuul con filtri personalizzati.

Qui vedremo come aggiungere la funzionalità di logout a quanto sopra.

Questa volta, utilizzeremo un'altra API Keycloak per disconnettere un utente. Richiameremo POST sull'endpoint di disconnessione per disconnettersi da una sessione tramite una chiamata non del browser , invece del reindirizzamento dell'URL che abbiamo usato nella sezione precedente.

3.1. Definisci percorso per il logout

Per cominciare, aggiungiamo un'altra rotta al proxy nel nostro 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

In effetti, abbiamo aggiunto una sotto-route all'autenticazione / aggiornamento già esistente . È importante che aggiungiamo il sotto-percorso prima del percorso principale, altrimenti Zuul mapperà sempre l'URL del percorso principale .

Abbiamo aggiunto una sub route invece di una principale per avere accesso al cookie refreshToken solo HTTP , che era impostato per avere un percorso molto limitato come / auth / refresh (e i suoi sottopercorsi). Vedremo perché abbiamo bisogno del cookie nella prossima sezione.

3.2. POST al server di autorizzazione / logout

Ora miglioriamo l' implementazione di CustomPreZuulFilter per intercettare l' URL / auth / refresh / revoke e aggiungere le informazioni necessarie da passare al server di autorizzazione.

I parametri del modulo richiesti per il logout sono simili a quelli della richiesta del token di aggiornamento, tranne per l'assenza di 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"); } //... } }

Qui, abbiamo semplicemente estratto il cookie refreshToken e inviato il formParams richiesto .

3.3. Rimuovi il token di aggiornamento

Quando si revoca il token di accesso utilizzando il reindirizzamento del logout come visto in precedenza, anche il token di aggiornamento ad esso associato viene invalidato dal server di autorizzazione.

Tuttavia, in questo caso, il cookie httpOnly rimarrà impostato sul client. Dato che non possiamo rimuoverlo tramite JavaScript, dobbiamo rimuoverlo dal lato server.

Per questo, aggiungiamo all'implementazione CustomPostZuulFilter che intercetta l' URL / auth / refresh / revoke in modo che rimuova il cookie refreshToken quando incontra questo 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. Rimuovere il token di accesso dal client angolare

Oltre a revocare il token di aggiornamento, sarà necessario rimuovere anche il cookie access_token dal lato client.

Aggiungiamo un metodo al nostro controller Angular che cancella il cookie access_token e chiama il mapping 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') ); }

Questa funzione verrà chiamata facendo clic sul pulsante Logout:

Logout

4. Conclusione

In questo tutorial veloce ma approfondito, abbiamo mostrato come possiamo disconnettere un utente da un'applicazione protetta OAuth e invalidare i token di quell'utente.

Il codice sorgente completo degli esempi può essere trovato su GitHub.