Oauth 2.1 Spring Authorization Server + SPA / Sudo Null IT News

Guten Tag an alle, liebe Chabroviter!

Bis jetzt war ich nur ein Leser dieser wunderbaren Ressource, aber jetzt scheint es an der Zeit zu sein, meinen ersten Artikel zu schreiben.

Oauth 2.1 ist eine Weiterentwicklung des beliebten Autorisierungsframeworks Oauth 2.0, das sich zum Zeitpunkt der Erstellung dieses Artikels noch im Entwurfsstadium befindet. Es beginnt jedoch bereits mit der Anwendung. Auf Habré gibt es bereits einen ausführlicheren Artikel zu diesem Thema.

Von nicht sehr angenehm wurden Optionen zum Erhalten eines Tokens aus Oauth 2.1 entfernt:

Aber im Gegenzug erhalten wir PKCE-Unterstützung für öffentliche und private Kunden.

Und jetzt möchte ich Ihrem Gericht ein kleines Beispiel für die Implementierung des Erhaltens von Tokens bringen Spring-Autorisierungsserver (Version 0.3.1 zum Zeitpunkt des Schreibens) und SPA auf Vue.js.

Etwas Code:

@Bean public RegistriertesClientRepository RegistriertesClientRepository() { RegistrierterClient RegistrierterClient = RegistrierterClient.withId(UUID.randomUUID().toString()) .clientId(“browser-client”) .clientSecret(“{noop}secret”) .clientAuthenticationMethod(ClientAuthenticationMethod.NONE ) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri(” .scope(OidcScopes.OPENID) .scope(“browser.read”) .build(); return new InMemoryRegisteredClientRepository(registeredClient); }

Wir registrieren den Client auf dem Server und interessieren uns zunächst für diese Zeile .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)

Es konfiguriert, wie unser Browser-Client autorisiert wird, im Fall von NONE ist keine Client-Autorisierung erforderlich, aber in diesem Fall wird nur access_token ausgegeben, und wenn id_token erforderlich ist, wird im Falle eines öffentlichen Clients refresh_token nicht ausgegeben .

Nun der Client-Code:

login() { var codeVerifier = this.generateRandomString(64); Promise.resolve() .then(() => { return this.generateCodeChallenge(codeVerifier) ​​​​}) .then(function(codeChallenge) { window.sessionStorage.setItem(“code_verifier”, codeVerifier) ​​​​let args = new URLSearchParams({response_type: “code”, client_id: ‘browser-client’, forward_uri: ‘ state: ‘1234zyx’, code_challenge: codeChallenge, code_challenge_method: ‘S256’, scope: ‘openid browser.read’ }); window.location = ” + args ; }); }, async generateCodeChallenge(codeVerifier) { var digest = await crypto.subtle.digest(“SHA-256″, new TextEncoder().encode(codeVerifier)); return btoa(String. fromCharCode(…new Uint8Array(digest))) .replace(/=/g, ”).replace(/\+/g, ‘-‘).replace(/\//g, ‘_’) } , generateRandomString(length) { var text = “”; var possible = “ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789”; for (var i = 0; i < length; i++) { text += possible.charAt(Math.floor(Math.random() * möglich.Länge)); } Rückgabetext; }

Wir bilden eine URL um zum Autorisierungsserver zu gehen, hier ist alles Standard, außer dass man client_secret nicht angeben muss, sondern 2 Felder code_challenge und code_challenge_method gebildet werden. code_challenge – alphanumerische beliebige Zeichenfolge und code_challenge_method – Verschlüsselungsmethode. Sie werden auf dem Server gespeichert und beim Austausch eines Zugangscodes gegen einen Token überprüft.

Außerdem müssen wir den Original-String window.sessionStorage.setItem(“code_verifier”, codeVerifier) ​​im Browser speichern, bei der Anfrage zum Austausch von Code gegen einen Token wird dieser String ebenfalls an den Server gesendet und dort verifiziert mit den zuvor gesendeten code_challenge und code_challenge_method. Hier ist der zweite Teil des Codes selbst, der einen Zugangscode gegen einen Token austauscht:

router.beforeEach((to, from, next) => { if (to.path == ‘/code’ && to.query.code != null) { let formData = new FormData() formData.append(‘grant_type’ ,’Autorisierungscode’) formData.append(‘code’,to.query.code) formData.append(‘redirect_uri’,’ formData.append(‘client_id’,’browser-client’) formData.append(‘code_verifier’, window.sessionStorage.getItem(“code_verifier”)) axios.post(‘ formData, { headers: { ‘Content-type’:’application/url-form-encoded’ } } ).then(resp => { console.log (resp.data) window.sessionStorage.setItem(“_a”, resp.data.access_token); }) next({name: ‘Index’}) } else { next() } })

Da ich Vue.js und vue-router verwendet habe, kümmert sich der Router selbst um das Abfangen des Anrufs. Wenn wir also einen Anruf mit dem /code-Pfad haben und der Code-Parameter in der Anfrage vorhanden ist, fängt der Router ihn ab, generiert ein Formular und sendet es an den Code-Austausch-Endpunkt für ein Token, und als Antwort erhalten wir das eigentliche access_token (und id_token, wenn wir . scope(OidcScopes.OPENID) konfiguriert haben und in der ersten Anfrage die Bereiche den Bereich haben: ‘openid’).

Jetzt ein paar Nuancen.

Wenn wir auf dem Server eine Client-Autorisierungsmethode .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) haben und in der Codeaustauschanforderung für ein Token den Header ‘Authorization’:’Basic ‘+btoa(‘browser-client:secret’) hinzufügen, dann wird unser Client vertraulich und in diesem Fall erhalten wir zusätzlich zum access_token auch ein refresh_token. Aber wie uns die Spezifikation sagt, sollte das Refresh-Token nicht im Browser gespeichert werden, da es keine Möglichkeit gibt, zu garantieren, dass es dort sicher gespeichert wird.

Der gesamte Code kann unter eingesehen werden GitHub.

Das ist alles, ich hoffe, der Artikel wird für jemanden nützlich und interessant sein.

Vielen Dank!

Similar Posts

Leave a Reply

Your email address will not be published.