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: Útvonalakat állítottunk fel a következők kezelésére: 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. A proxy első használata egyszerű - létrehoztunk egy kérést az engedélyezési kód megszerzésére: 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. 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): 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: Itt állítjuk be az attribútumot szigorú, így a cookie-k webhelyek közötti átadását szigorúan visszatartják. 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: É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: 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. 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): 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. 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: 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. 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 (): 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. 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: 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. 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.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 /
4. Szerezze be a kódot a Zuul Pre Filter használatával
@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"; }}
5. Helyezze a kódot egy cookie-ba Használata Zuul Post Filter
@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"; }}
@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); }; }}
6. Szerezze be és használja a Cookie kódját
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; }
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"; }}
7. Helyezze a Frissítési tokent egy cookie-ba
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
8. Szerezze be és használja a Cookie frissítő tokent
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 (); }} ...}
9. Az Access Token frissítése szögből
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')); }
10. Futtassa a kezelőfelületet
mvn tiszta telepítés
11. Következtetés