OAuth2 egy tavaszi REST API-hoz - Kezelje a frissítési tokent szögben

1. Áttekintés

Ebben az oktatóanyagban folytatjuk az OAuth2 engedélyezési kód folyamatának feltárását, amelyet előző cikkünkben kezdtünk összerakni, és arra fogunk összpontosítani, hogy miként kezeljük a frissítési tokent egy szögletes alkalmazásban. Használjuk a Zuul proxy-t is.

Az OAuth-csomagot a Spring Security 5-ben fogjuk használni. Ha használni szeretné a Spring Security OAuth örökölt veremét, olvassa el ezt a korábbi cikket: OAuth2 egy Spring REST API-hoz - A Frissítési token kezelése az AngularJS-ben (régi OAuth-verem)

2. Hozzáférési token lejárata

Először is, ne feledje, hogy az ügyfél két lépésben szerzett hozzáférési tokent egy Engedélyezési kód engedélytípus segítségével. Első lépésben megszerezzük az Engedélyezési Kódot. A második lépésben valóban megszerezzük az Access Tokent.

Hozzáférési tokenünket egy olyan cookie tárolja, amely lejár, attól függően, hogy maga a token lejár-e:

var expireDate = new Date (). getTime () + (1000 * token.expires_in); Cookie.set ("access_token", token.access_token, expireDate);

Amit fontos megérteni, az az magát a sütit csak tárolásra használják és nem hajt mást az OAuth2 folyamatban. Például a böngésző soha nem küldi el automatikusan a cookie-kat a szerverhez kérésekkel, így itt biztonságban vagyunk.

De vegye figyelembe, hogyan definiáljuk ezt valójában retrieveToken () függvény az Access Token megszerzéséhez:

retrieveToken (code) {let params = new URLSearchParams (); params.append ('grant_type', 'authorization_code'); params.append ('client_id', this.clientId); params.append ('client_secret', 'newClientSecret'); params.append ('redirect_uri', this.redirectUri); params.append ('kód', kód); let fejlécek = new HttpHeaders ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8'}); this._http.post ('// localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / token', params.toString (), {headers: header}) .subscribe (data => this.saveToken ( adatok), err => alert ('Érvénytelen hitelesítő adatok')); }

Az ügyféltitkot elküldjük a params, ami valójában nem biztonságos módszer ennek kezelésére. Lássuk, hogyan kerülhetjük el ezt.

3. A meghatalmazott

Így, most egy Zuul-proxy fut majd a front-end alkalmazásban, és alapvetően a front-end kliens és a Authorization Server között ülünk. Az összes bizalmas információt ebben a rétegben fogjuk kezelni.

A front-end kliens mostantól Boot alkalmazásként lesz tárolva, így a Spring Cloud Zuul starter segítségével zökkenőmentesen csatlakozhatunk beágyazott Zuul-proxynkhoz.

Ha át akarja tekinteni a Zuul alapjait, olvassa el gyorsan a Zuul fő cikkét.

Most konfiguráljuk a proxy útvonalait:

zuul: útvonalak: auth / code: elérési út: / auth / code / ** érzékeny Fejlécek: url: // localhost: 8083 / auth / realms / baeldung / protokoll / openid-connect / auth auth / token: elérési út: / auth / token / ** sensitiveHeaders: url: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / token auth / refresh: elérési út: / auth / refresh / ** sensitiveHeaders: url: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / token auth / redirect: elérési út: / auth / redirect / ** sensitiveHeaders: url: // localhost: 8089 / auth / resources: elérési út: / auth / resources / ** sensitiveHeaders: URL: // localhost: 8083 / auth / resources /

Útvonalakat állítottunk fel a következők kezelésére:

  • hitelesítő kód - szerezze be az engedélyezési kódot, és mentse el egy cookie-ba
  • auth / redirect - kezelje az átirányítást a Jogosultságkiszolgáló bejelentkezési oldalára
  • auth / erőforrások - hozzárendelés az engedélyezési szerver megfelelő elérési útjához a bejelentkezési oldal erőforrásaihoz (css és js)
  • auth / token - szerezze be az Access Tokent, távolítsa el refresh_token a hasznos teherből, és mentse el egy cookie-ban
  • hitelesítés / frissítés - szerezze be a Refresh Tokent, távolítsa el a hasznos teherről, és mentse el egy cookie-ba

Ami itt érdekes, hogy csak a Authorization Server forgalmát irányítjuk, és semmi mást. Csak akkor van szükségünk a proxy-ra, amikor bejön a kliens új tokenekre.

Ezután nézzük meg mindezeket egyenként.

4. Szerezze be a kódot a Zuul Pre Filter használatával

A proxy első használata egyszerű - létrehoztunk egy kérést az engedélyezési kód megszerzésére:

@Component public class CustomPreZuulFilter kiterjeszti a ZuulFilter {@Orride public Object run () {RequestContext ctx = RequestContext.getCurrentContext (); HttpServletRequest req = ctx.getRequest (); Karakterlánc kérésURI = req.getRequestURI (); if (requestURI.contains ("auth / code")) {Map params = ctx.getRequestQueryParams (); if (params == null) {params = Maps.newHashMap (); } params.put ("response_type", Lists.newArrayList (új karakterlánc [] {"kód"})); params.put ("hatókör", Lists.newArrayList (új karakterlánc [] {"read"})); params.put ("client_id", Lists.newArrayList (új karakterlánc [] {CLIENT_ID})); params.put ("redirect_uri", Lists.newArrayList (új karakterlánc [] {REDIRECT_URL})); ctx.setRequestQueryParams (paraméterek); } return null; } @Orride public boolean shouldFilter () {logikai képlet = hamis; RequestContext ctx = RequestContext.getCurrentContext (); Karakterlánc URI = ctx.getRequest (). GetRequestURI (); if (URI.contains ("auth / code") || URI.contains ("auth / token") || URI.contains ("auth / refresh")) {shouldfilter = true; } return kétszűrő; } @Orride public int filterOrder () {return 6; } @Orride public String filterType () {return "pre"; }}

Szűrőtípust használunk elő hogy a kérelmet továbbadás előtt feldolgozza.

A szűrőben fuss() metódushoz, lekérdezési paramétereket adunk a response_type, hatálya, Ügyfélazonosító és átirányítás_uri- minden, amire a Hitelesítés-kiszolgálónknak szüksége van, hogy a bejelentkezési oldalára vigyen minket, és visszaküldjön egy kódot.

Szintén vegye figyelembe a shouldFilter () módszer. A kéréseket csak az említett 3 URI-val szűrjük, mások nem mennek át az fuss módszer.

5. Helyezze a kódot egy cookie-ba Használata Zuul Post Filter

Amit itt tervezünk, az az, hogy mentse a kódot cookie-ként, hogy elküldhessük az Authorization Server-nek az Access Token megszerzéséhez. A kód lekérdezési paraméterként szerepel a kérés URL-jében, amelyre az Authorization Server átirányít minket bejelentkezés után.

Felállítunk egy Zuul utószűrőt a kód kibontásához, és beillesztjük a cookie-ba. Ez nem csak egy normális süti, hanem a biztonságos, csak HTTP-s süti, nagyon korlátozott elérési úttal (/ auth / token):

@Component public class A CustomPostZuulFilter kiterjeszti a ZuulFilter {private ObjectMapper mapper = new ObjectMapper (); @Orride public Object run () {RequestContext ctx = RequestContext.getCurrentContext (); próbáld ki a {Map params = ctx.getRequestQueryParams (); if (requestURI.contains ("auth / redirect")) {Cookie cookie = új Cookie ("code", params.get ("code"). get (0)); cookie.setHttpOnly (true); cookie.setPath (ctx.getRequest (). getContextPath () + "/ auth / token"); ctx.getResponse (). addCookie (süti); }} catch (e kivétel) {logger.error ("Hiba történt a zuul post szűrőben", e); } return null; } @Orride public boolean shouldFilter () {logikai képlet = = hamis; RequestContext ctx = RequestContext.getCurrentContext (); Karakterlánc URI = ctx.getRequest (). GetRequestURI (); if (URI.contains ("auth / redirect") || URI.contains ("auth / token") || URI.contains ("auth / refresh")) {shouldfilter = true; } return kétszűrő; } @Orride public int filterOrder () {return 10; } @Orride public String filterType () {return "post"; }}

A CSRF-támadások elleni további védelmi réteg hozzáadása érdekében minden sütinkhez hozzáadunk egy Same-Site cookie fejlécet.

Ehhez létrehozunk egy konfigurációs osztályt:

@Configuration public class SameSiteConfig implementálja a WebMvcConfigurer {@Bean public TomcatContextCustomizer sameSiteCookiesConfig () {return context -> {final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor (); cookieProcessor.setSameSiteCookies (SameSiteCookies.STRICT.getValue ()); context.setCookieProcessor (cookieProcessor); }; }}

Itt állítjuk be az attribútumot szigorú, így a cookie-k webhelyek közötti átadását szigorúan visszatartják.

6. Szerezze be és használja a Cookie kódját

Most, hogy megvan a Kód a cookie-ban, amikor a kezelő Angular alkalmazás megpróbál kiváltani egy Token kérést, elküldi a kérést a következő címre: / auth / token és így a böngésző természetesen elküldi ezt a sütit.

Tehát most lesz egy másik feltételünk elő szűrje a proxyban, hogy kibontja a kódot a cookie-ból és elküldi más űrlapparaméterekkel együtt a Token megszerzéséhez:

public Object run () {RequestContext ctx = RequestContext.getCurrentContext (); ... egyébként ha (requestURI.contains ("auth / token")))) {próbálkozzon {String code = extractCookie (req, "code"); String formParams = String.format ("grant_type =% s & client_id =% s & client_secret =% s & redirect_uri =% s & code =% s", "autorizációs_kód", CLIENT_ID, CLIENT_SECRET, REDIRECT_URL, kód); bájt [] bájt = formParams.getBytes ("UTF-8"); ctx.setRequest (új CustomHttpServletRequest (req, bájt)); } catch (IOException e) {e.printStackTrace (); }} ...} private String extractCookie (HttpServletRequest req, String name) {Cookie [] cookie = req.getCookies (); if (cookie-k! = null) {for (int i = 0; i <cookie.hossz; i ++) {if (cookie-k [i] .getName (). equalsIgnoreCase (név)) {return cookies [i] .getValue () ; }}} return null; }

És itt van a miénkCustomHttpServletRequest - a kérelem törzsének elküldésére szolgál, a szükséges űrlapparaméterekkel bájtokká konvertálva:

public class CustomHttpServletRequest kiterjeszti a HttpServletRequestWrapper {private byte [] bájtokat; public CustomHttpServletRequest (HttpServletRequest kérés, bájt [] bájt) {szuper (kérés); ez.bájt = bájt; } @Orride public ServletInputStream getInputStream () dobja az IOException-t {return new ServletInputStreamWrapper (byte); } @Orride public int getContentLength () {return bytes.length; } @Orride public long getContentLengthLong () {return bytes.length; } @Orride public String getMethod () {return "POST"; }}

Ezzel hozzáférési tokent kapunk az Authorization Server-től a válaszban. Ezután meglátjuk, hogyan alakítjuk át a választ.

7. Helyezze a Frissítési tokent egy cookie-ba

A szórakoztató dolgokról.

Amit itt tervezünk, az az, hogy az ügyfél cookie-ként megkapja a Frissítési tokent.

Hozzáadjuk a Zuul utószűrőnket, hogy kivonjuk a Frissítési tokent a válasz JSON törzséből, és beállítsuk a cookie-ba. Ez ismét egy biztonságos, csak HTTP-s süti, nagyon korlátozott elérési úttal (/ auth / refresh):

public Object run () {... else if (requestURI.contains ("auth / token") || requestURI.contains ("auth / refresh")) {InputStream = ctx.getResponseDataStream (); String responseBody = IOUtils.toString (azaz "UTF-8"); if (responseBody.contains ("refresh_token")) {Map responseMap = mapper.readValue (responseBody, új TypeReference() {}); Karakterlánc refreshToken = responseMap.get ("refresh_token"). ToString (); responseMap.remove ("refresh_token"); responseBody = mapper.writeValueAsString (responseMap); Cookie cookie = új Cookie ("refreshToken", refreshToken); cookie.setHttpOnly (true); cookie.setPath (ctx.getRequest (). getContextPath () + "/ auth / refresh"); cookie.setMaxAge (2592000); // 30 nap ctx.getResponse (). AddCookie (cookie); } ctx.setResponseBody (responseBody); } ...}

Mint láthatjuk, itt hozzáadtunk egy feltételt a Zuul utószűrőnkbe, hogy elolvassuk a választ, és kivonjuk az útvonalak frissítő tokent auth / token és hitelesítés / frissítés. Pontosan ugyanazt tesszük a kettőért, mert az Authorization Server lényegében ugyanazt a hasznos terhet küldi, miközben megszerzi az Access Tokent és a Refresh Token-t.

Aztán eltávolítottuk refresh_token a JSON-választól annak biztosítása érdekében, hogy az soha ne legyen elérhető a cookie-n kívüli kezelőfelület számára.

Egy másik szempont, amelyet itt meg kell jegyeznünk, hogy a süti maximális életkorát 30 napra állítottuk be - mivel ez megegyezik a Token lejárati idejével.

8. Szerezze be és használja a Cookie frissítő tokent

Most, hogy megvan a Frissítés Token a cookie-ban, amikor a front-end Angular alkalmazás megpróbálja kiváltani a token frissítését, a kérést a következő címre küldi: / auth / refresh és így a böngésző természetesen elküldi ezt a sütit.

Tehát most lesz egy másik feltételünk elő szűrje a proxyban, amely kibontja a frissítési tokent a cookie-ból, és HTTP paraméterként továbbítja - hogy a kérés érvényes legyen:

public Object run () {RequestContext ctx = RequestContext.getCurrentContext (); ... különben, ha (requestURI.contains ("auth / refresh")))) {próbálkozzon {String token = extractCookie (req, "token"); String formParams = String.format ("grant_type =% s & client_id =% s & client_secret =% s & refresh_token =% s", "refresh_token", CLIENT_ID, CLIENT_SECRET, token); bájt [] bájt = formParams.getBytes ("UTF-8"); ctx.setRequest (új CustomHttpServletRequest (req, bájt)); } catch (IOException e) {e.printStackTrace (); }} ...}

Ez hasonló ahhoz, amit akkor tettünk, amikor először megszereztük az Access Tokent. De vegye észre, hogy a formatest más. Most küldünk egy grant_type nak,-nek refresh_token ahelyett megerősítő kód azzal a jelzővel együtt, amelyet korábban a cookie-ba mentettünk.

A válasz megszerzése után ismét ugyanazon az átalakuláson megy keresztül a elő szűrő, amint azt korábban a 7. szakaszban láttuk.

9. Az Access Token frissítése szögből

Végül módosítsuk egyszerű kezelőfelületünk alkalmazását, és valóban használjuk ki a token frissítését:

Itt van a funkciónk refreshAccessToken ():

refreshAccessToken () {let headers = new HttpHeaders ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8'}); this._http.post ('auth / refresh', {}, {header: headers}) .subscribe (data => this.saveToken (data), err => alert ('Érvénytelen hitelesítő adatok')); }

Vegye figyelembe, hogyan használjuk egyszerűen a meglévőket saveToken () függvény - és csak különböző bemeneteket ad át neki.

Ezt is vedd észre nem adunk hozzá űrlapparamétereket a refresh_token magunkat - mivel erről a Zuul szűrő gondoskodni fog.

10. Futtassa a kezelőfelületet

Mivel a front-end Angular kliensünket most Boot alkalmazásként tárolják, a futtatása kissé más lesz, mint korábban.

Az első lépés ugyanaz. Ki kell építenünk az alkalmazást:

mvn tiszta telepítés

Ez kiváltja a frontend-maven-plugin a mi pom.xml az Angular kód felépítéséhez és a felhasználói felület műtermékeinek átmásolásához ide cél / osztályok / statikus mappába. Ez a folyamat felülír minden mást, ami a src / main / resources Könyvtár. Tehát meg kell győződnünk arról, hogy tartalmaz-e ebből a mappából minden szükséges erőforrást, például alkalmazás.yml, a másolási folyamatban.

A második lépésben futtatnunk kell SpringBootApplication osztály UiApplication. Ügyfélalkalmazásunk a 8089-es porton működik, és a alkalmazás.yml.

11. Következtetés

Ebben az OAuth2 oktatóanyagban megtanultuk, hogyan kell a Frissítési tokent egy Angular ügyfélalkalmazásban tárolni, hogyan kell frissíteni egy lejárt hozzáférési tokent, és hogyan lehet mindehhez felhasználni a Zuul proxyt.

Az oktatóanyag teljes megvalósítása megtalálható a GitHub oldalon.