OAuth2 pre jarné REST API - spracovajte obnovovací token v uhle

1. Prehľad

V tomto tutoriále budeme pokračovať v skúmaní toku autorizačného kódu OAuth2, ktorý sme začali dávať dohromady v našom predchádzajúcom článku a zameriame sa na to, ako zaobchádzať s obnovovacím tokenom v aplikácii Angular. Budeme tiež využívať proxy server Zuul.

Zásobník OAuth použijeme v Spring Security 5. Ak chcete použiť starší zásobník Spring Security OAuth, pozrite si tento predchádzajúci článok: OAuth2 pre Spring REST API - spracovajte obnovovací token v AngularJS (starší zásobník OAuth)

2. Vypršanie platnosti tokenu

Najprv nezabudnite, že klient získaval prístupový token pomocou typu grantu autorizačného kódu v dvoch krokoch. V prvom kroku získame Autorizačný kód. A v druhom kroku vlastne získame prístupový token.

Náš prístupový token je uložený v súbore cookie, ktorého platnosť vyprší na základe expirácie samotného tokenu:

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

Dôležité je pochopiť to samotný súbor cookie sa používa iba na ukladanie a v toku OAuth2 to nič iné nevnáša. Prehliadač napríklad nikdy automaticky neodošle súbor cookie na server s požiadavkami, takže sme tu zabezpečení.

Ale všimnite si, ako to vlastne definujeme retrieveToken () funkcia na získanie prístupového tokenu:

retrieveToken (code) {let params = new URLSearchParams (); params.append ('grant_type', 'autorizačný_kód'); params.append ('client_id', this.clientId); params.append ('client_secret', 'newClientSecret'); params.append ('redirect_uri', this.redirectUri); params.append ('code', code); let headers = 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: headers}) .subscribe (data => this.saveToken ( data), err => alert ('Invalid Credentials')); }

Klientovi posielame tajomstvo v params, čo v skutočnosti nie je bezpečný spôsob riešenia. Pozrime sa, ako sa tomu môžeme vyhnúť.

3. Proxy server

Takže teraz budeme mať proxy server Zuul bežiaci v klientskej aplikácii a v podstate sedieť medzi klientom front-end a autorizačným serverom. Všetky citlivé informácie budú spracované v tejto vrstve.

Klient front-end bude teraz hostený ako aplikácia Boot, aby sme sa mohli bezproblémovo pripojiť k nášmu integrovanému serveru Zuul pomocou štartovacieho modulu Spring Cloud Zuul.

Ak si chcete prečítať základné informácie o Zuule, prečítajte si rýchlo hlavný článok o Zuule.

Teraz nakonfigurujme trasy proxy:

zuul: route: auth / code: path: / auth / code / ** sensitiveHeaders: url: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / auth auth / token: path: / auth / token / ** sensitiveHeaders: url: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / token auth / refresh: cesta: / auth / refresh / ** sensitiveHeaders: url: // localhost: 8083 / auth / realms / baeldung / protocol / openid-connect / token auth / redirect: path: / auth / redirect / ** sensitiveHeaders: url: // localhost: 8089 / auth / resources: path: / auth / resources / ** sensitiveHeaders: url: // localhost: 8083 / auth / resources /

Nastavili sme trasy, ktoré zvládnu nasledujúce:

  • auth / kód - získajte autorizačný kód a uložte ho do súboru cookie
  • auth / redirect - spracovať presmerovanie na prihlasovaciu stránku autorizačného servera
  • auth / zdroje - namapovať na zodpovedajúcu cestu autorizačného servera pre zdroje jeho prihlasovacej stránky (css a js)
  • auth / token - získať prístupový token, odstrániť refresh_token z užitočného zaťaženia a uložte ho do súboru cookie
  • auth / refresh - získajte obnovovací token, odstráňte ho z užitočného zaťaženia a uložte ho do súboru cookie

Zaujímavé tu je, že prenášame prenosy iba na server autorizácie a nie na nič iné. Skutočne potrebujeme iba proxy, aby prišiel, keď klient získava nové tokeny.

Ďalej sa pozrime na všetky tieto jeden po druhom.

4. Získajte kód pomocou filtra Zuul Pre Filter

Prvé použitie proxy je jednoduché - nastavili sme požiadavku na získanie Autorizačného kódu:

@Component public class CustomPreZuulFilter rozširuje ZuulFilter {@Override public Object run () {RequestContext ctx = RequestContext.getCurrentContext (); HttpServletRequest req = ctx.getRequest (); Reťazec requestURI = req.getRequestURI (); if (requestURI.contains ("auth / code")) {Map params = ctx.getRequestQueryParams (); if (params == null) {params = Maps.newHashMap (); } params.put ("response_type", Lists.newArrayList (nový reťazec [] {"kód"})); params.put ("rozsah", Lists.newArrayList (nový reťazec [] {"čítať"})); params.put ("client_id", Lists.newArrayList (nový reťazec [] {CLIENT_ID})); params.put ("redirect_uri", Lists.newArrayList (nový reťazec [] {REDIRECT_URL})); ctx.setRequestQueryParams (parametre); } return null; } @Override public boolean shouldFilter () {boolean shouldfilter = false; RequestContext ctx = RequestContext.getCurrentContext (); Reťazec URI = ctx.getRequest (). GetRequestURI (); if (URI.contains ("auth / code") || URI.contains ("auth / token") || URI.contains ("auth / refresh")) {shouldfilter = true; } return shouldfilter; } @Override public int filterOrder () {návrat 6; } @Override public String filterType () {return "pre"; }}

Používame typ filtra pre pred odoslaním žiadosť spracovať.

Vo filtri run () metóda, pridáme parametre dotazu pre typ_odpovede, rozsah, client_id a presmerovanie_uri- všetko, čo náš autorizačný server potrebuje, aby nás zaviedol na svoju prihlasovaciu stránku a poslal späť kód.

Všimnite si tiež shouldFilter () metóda. Filtrujeme iba žiadosti so spomenutými 3 URI, iné neprechádzajú na bežať metóda.

5. Vložte kód do súboru cookie Použitím Zuul Post Filter

To, čo tu plánujeme urobiť, je uložiť kód ako súbor cookie, aby sme ho mohli poslať ďalej na autorizačný server, aby sme získali prístupový token. Kód je prítomný ako parameter dopytu v adrese URL požiadavky, na ktorú nás autorizačný server presmeruje po prihlásení.

Nastavíme post-filter Zuul na extrahovanie tohto kódu a jeho nastavenie v súbore cookie. Toto nie je len normálny súbor cookie, ale zabezpečený súbor cookie iba pre HTTP s veľmi obmedzenou cestou (/ auth / token):

@Component public class CustomPostZuulFilter rozširuje ZuulFilter {private ObjectMapper mapper = new ObjectMapper (); @Override public Object run () {RequestContext ctx = RequestContext.getCurrentContext (); try {Map params = ctx.getRequestQueryParams (); if (requestURI.contains ("auth / redirect")) {Cookie cookie = new Cookie ("code", params.get ("code"). get (0)); cookie.setHttpOnly (true); cookie.setPath (ctx.getRequest (). getContextPath () + "/ auth / token"); ctx.getResponse (). addCookie (cookie); }} catch (Výnimka e) {logger.error ("Chyba nastala vo filtri príspevku zuul", e); } return null; } @Override public boolean shouldFilter () {boolean shouldfilter = false; RequestContext ctx = RequestContext.getCurrentContext (); Reťazec URI = ctx.getRequest (). GetRequestURI (); if (URI.contains ("auth / redirect") || URI.contains ("auth / token") || URI.contains ("auth / refresh")) {shouldfilter = true; } return shouldfilter; } @Override public int filterOrder () {návrat 10; } @Override public String filterType () {return "post"; }}

S cieľom pridať ďalšiu vrstvu ochrany pred útokmi CSRF, do všetkých našich súborov cookie pridáme hlavičku súboru cookie rovnakého servera.

Za týmto účelom vytvoríme konfiguračnú triedu:

@Configuration verejná trieda SameSiteConfig implementuje WebMvcConfigurer {@Bean public TomcatContextCustomizer sameSiteCookiesConfig () {návratový kontext -> {final Rfc6265CookieProcessor cookieProcessor = nový Rfc6265CookieProcessor (); cookieProcessor.setSameSiteCookies (SameSiteCookies.STRICT.getValue ()); context.setCookieProcessor (cookieProcessor); }; }}

Tu nastavujeme atribút na prísny, aby bol akýkoľvek prenos súborov cookie medzi stránkami prísne zadržaný.

6. Získajte a používajte kód z cookies

Teraz, keď máme kód v súbore cookie, keď sa front-endová uhlová aplikácia pokúsi spustiť požiadavku na token, pošle žiadosť na / auth / token a tak prehliadač tento súbor cookie samozrejme pošle.

Takže teraz budeme mať ďalšiu podmienku v našej pre filter v serveri proxy, ktorý extrahuje Kód z cookie a odošle ho spolu s ďalšími parametrami formulára na získanie Tokenu:

public Object run () {RequestContext ctx = RequestContext.getCurrentContext (); ... else if (requestURI.contains ("auth / token"))) {try {String code = extractCookie (req, "code"); String formParams = String.format ("grant_type =% s & client_id =% s & client_secret =% s & redirect_uri =% s & code =% s", "autorizačný_kód", CLIENT_ID, CLIENT_SECRET, REDIRECT_URL, kód); byte [] bytes = formParams.getBytes ("UTF-8"); ctx.setRequest (nový CustomHttpServletRequest (požiadavka, bajty)); } catch (IOException e) {e.printStackTrace (); }} ...} private String extractCookie (HttpServletRequest req, názov reťazca) {Cookie [] cookies = req.getCookies (); if (cookies! = null) {for (int i = 0; i <cookies.length; i ++) {if (cookies [i] .getName (). equalsIgnoreCase (name)) {return cookies [i] .getValue () ; }}} return null; }

A tu je nášCustomHttpServletRequest - slúži na odoslanie nášho tela žiadosti s požadovanými parametrami formulára prevedenými na bajty:

verejná trieda CustomHttpServletRequest rozširuje HttpServletRequestWrapper {private byte [] bajtov; public CustomHttpServletRequest (požiadavka HttpServletRequest, byte [] bajtov) {super (požiadavka); this.bytes = bytes; } @Override public ServletInputStream getInputStream () vyvolá IOException {vrátiť nový ServletInputStreamWrapper (bajty); } @Override public int getContentLength () {return bytes.length; } @Override public long getContentLengthLong () {return bytes.length; } @Override public String getMethod () {return "POST"; }}

Získame tým prístupový token z autorizačného servera v odpovedi. Ďalej uvidíme, ako transformujeme odpoveď.

7. Vložte obnovovací token do súboru cookie

Ďalej k zábavným veciam.

To, čo tu plánujeme urobiť, je dosiahnuť, aby klient dostal obnovovací token ako súbor cookie.

Pridáme do nášho post-filtra Zuul, aby sme extrahovali obnovovací token z tela JSON odpovede a nastavili ho do cookie. Toto je opäť zabezpečený súbor cookie iba pre HTTP s veľmi obmedzenou cestou (/ auth / refresh):

public Object run () {... else if (requestURI.contains ("auth / token") || requestURI.contains ("auth / refresh")) {InputStream is = ctx.getResponseDataStream (); Reťazec responseBody = IOUtils.toString (je "UTF-8"); if (responseBody.contains ("refresh_token")) {Map responseMap = mapper.readValue (responseBody, nový TypeReference() {}); Reťazec refreshToken = responseMap.get ("refresh_token"). ToString (); responseMap.remove ("refresh_token"); responseBody = mapper.writeValueAsString (responseMap); Cookie cookie = nový Cookie ("refreshToken", refreshToken); cookie.setHttpOnly (true); cookie.setPath (ctx.getRequest (). getContextPath () + "/ auth / refresh"); cookie.setMaxAge (2592000); // 30 dní ctx.getResponse (). AddCookie (cookie); } ctx.setResponseBody (responseBody); } ...}

Ako vidíme, tu sme do nášho post-filtra Zuul pridali podmienku na prečítanie odpovede a extrahovanie obnovovacieho tokenu pre trasy auth / token a auth / refresh. Robíme pre tých dvoch presne to isté, pretože autorizačný server v zásade posiela rovnaké užitočné zaťaženie pri získavaní prístupového tokenu a obnovovacieho tokenu.

Potom sme odstránili refresh_token z odpovede JSON, aby ste sa uistili, že nikdy nie je prístupný klientskemu rozhraniu mimo súboru cookie.

Ďalej je potrebné poznamenať, že sme nastavili maximálny vek súboru cookie na 30 dní - pretože sa to zhoduje s časom platnosti tokenu.

8. Získajte a použite obnovovací token z cookie

Teraz, keď máme v súbore cookie obnovovací token, keď sa front-endová uhlová aplikácia pokúsi spustiť obnovenie tokenu, pošleme žiadosť na / auth / refresh a tak prehliadač tento súbor cookie samozrejme pošle.

Takže teraz budeme mať ďalšiu podmienku v našej pre filter v serveri proxy, ktorý extrahuje obnovovací token zo súboru cookie a odošle ho ďalej ako parameter HTTP - aby bola žiadosť platná:

public Object run () {RequestContext ctx = RequestContext.getCurrentContext (); ... else if (requestURI.contains ("auth / refresh"))) {try {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); byte [] bytes = formParams.getBytes ("UTF-8"); ctx.setRequest (nový CustomHttpServletRequest (požiadavka, bajty)); } catch (IOException e) {e.printStackTrace (); }} ...}

Je to podobné tomu, čo sme robili, keď sme prvýkrát získali prístupový token. Ale všimnite si, že forma tela je iná. Teraz posielame a grant_type z refresh_token namiesto autorizačný kód spolu s tokenom, ktorý sme predtým uložili do súboru cookie.

Po získaní odpovede opäť prechádza rovnakou transformáciou v pre filter, ako sme videli predtým v časti 7.

9. Obnovenie prístupového tokenu z uhla

Na záver poďme upraviť našu jednoduchú front-end aplikáciu a skutočne využiť obnovenie tokenu:

Tu je naša funkcia refreshAccessToken ():

refreshAccessToken () {let headers = new HttpHeaders ({'Content-type': 'application / x-www-form-urlencoded; charset = utf-8'}); this._http.post ('auth / refresh', {}, {headers: headers}) .subscribe (data => this.saveToken (data), err => alert ('Invalid Credentials')); }

Všimnite si, ako jednoducho používame existujúce saveToken () funkcie - a iba odovzdávať rôzne vstupy.

Všimnite si to tiež nepridávame žiadne parametre formulára s refresh_token sami - keďže sa o to postará filter Zuul.

10. Spustite klientske rozhranie

Pretože náš front-endový úhlový klient je teraz hostený ako bootovacia aplikácia, jeho prevádzka sa bude mierne líšiť ako doteraz.

Prvý krok je rovnaký. Musíme vytvoriť aplikáciu:

mvn čistá inštalácia

To spustí frontend-maven-plugin definované v našom pom.xml na zostavenie uhlového kódu a kopírovanie artefaktov používateľského rozhrania do cieľ / triedy / statické priečinok. Tento proces prepíše čokoľvek iné, čo máme v src / main / resources adresár. Musíme sa teda uistiť a zahrnúť všetky požadované zdroje z tohto priečinka, ako napríklad aplikácia.yml, v procese kopírovania.

V druhom kroku musíme spustiť našu SpringBootApplication trieda UiAplikácia. Naša klientská aplikácia bude v prevádzke na porte 8089, ako je uvedené v aplikácia.yml.

11. Záver

V tomto výučbe OAuth2 sme sa naučili, ako ukladať obnovovací token v klientskej aplikácii Angular, ako aktualizovať vypršaný prístupový token a ako na to všetko využiť proxy server Zuul.

Celú implementáciu tohto tutoriálu nájdete na GitHub.


$config[zx-auto] not found$config[zx-overlay] not found