Jarná podpora WebClient a OAuth2

1. Prehľad

Spring Security 5 poskytuje podporu OAuth2 pre neblokovanie Spring Webflux Webový klient trieda.

V tomto tutoriáli budeme analyzovať rôzne prístupy k prístupu k zabezpečeným zdrojom pomocou tejto triedy.

Tiež sa pozrieme pod kapotu, aby sme pochopili, ako Spring spracováva proces autorizácie OAuth2.

2. Nastavenie scenára

V súlade so špecifikáciou OAuth2 potrebujeme okrem nášho klienta, ktorý je predmetom nášho zamerania v tomto článku, prirodzene aj autorizačný server a zdrojový server.

Môžeme použiť známych poskytovateľov autorizácie ako Google alebo Github. Aby sme lepšie pochopili úlohu klienta OAuth2, môžeme tiež použiť naše vlastné servery s implementáciou, ktorá je k dispozícii tu. Nebudeme zobrazovať úplnú konfiguráciu, pretože to nie je témou tohto tutoriálu, stačí vedieť, že:

  • autorizačný server bude:
    • beží na porte 8081
    • vystavuje / oauth / autorizovať,/ oauth / token a oauth / check_token koncové body na vykonanie požadovanej funkčnosti
    • nakonfigurované s ukážkovými používateľmi (napr. john/123) a jeden klient OAuth (fooClientIdPassword/tajomstvo)
  • zdrojový server bude oddelený od autentifikačného servera a bude:
    • beží na porte 8082
    • slúžiace jednoduché Foo objekt zabezpečený zdroj prístupný pomocou / foos / {id} koncový bod

Poznámka: Je dôležité si uvedomiť, že niekoľko jarných projektov ponúka rôzne funkcie a implementácie súvisiace s OAuth. Môžeme preskúmať, čo každá knižnica poskytuje v tejto matici jarných projektov.

The Webový klient a všetky reaktívne funkcie súvisiace s Webfluxom sú súčasťou projektu Spring Security 5. Preto tento rámec budeme používať hlavne v tomto článku.

3. Jarná bezpečnosť 5 Pod kapotou

Aby sme úplne porozumeli nasledujúcim príkladom, je dobré vedieť, ako Spring Security interne spravuje funkcie OAuth2.

Tento rámec ponúka možnosti na:

  • pri prihlasovaní používateľov do aplikácie sa spoliehajte na účet poskytovateľa OAuth2
  • nakonfigurovať našu službu ako klienta OAuth2
  • spravovať za nás postupy autorizácie
  • obnovovať tokeny automaticky
  • v prípade potreby uložte poverenia

Niektoré zo základných konceptov sveta OAuth2 Spring Security sú popísané v nasledujúcom diagrame:

3.1. Poskytovatelia

Jar definuje rolu poskytovateľa OAuth2, ktorá je zodpovedná za vystavenie chránených zdrojov OAuth 2.0.

V našom príklade bude naša služba overovania tá, ktorá ponúka možnosti poskytovateľa.

3.2. Registrácie klientov

A ClientRegistration je subjekt obsahujúci všetky príslušné informácie o konkrétnom klientovi zaregistrovaný v poskytovateľovi OAuth2 (alebo OpenID).

V našom scenári to bude klient zaregistrovaný na autentifikačnom serveri, ktorý bude označený bael-client-id id.

3.3. Oprávnení klienti

Akonáhle koncový používateľ (alias vlastník zdrojov) udelí klientovi povolenia na prístup k jeho zdrojom, OAuth2AuthorizedClient entita je vytvorená.

Bude zodpovedná za priradenie prístupových tokenov k registráciám klientov a vlastníkom prostriedkov (zastúpeným Principal predmety).

3.4. Úložiská

Okrem toho Spring Security ponúka aj triedy úložiska na prístup k vyššie uvedeným entitám.

Najmä ReactiveClientRegistrationRepository a ServerOAuth2AuthorizedClientRepository triedy sa používajú v reaktívnych zásobníkoch a predvolene používajú pamäť v pamäti.

Spring Boot 2.x vytvára fazuľa týchto tried úložiska a automaticky ich pridáva do kontextu.

3.5. Reťazec bezpečnostného webového filtra

Jedným z kľúčových konceptov jarnej bezpečnosti 5 je reaktívny SecurityWebFilterChain subjekt.

Ako naznačuje jeho názov, predstavuje pripútanú zbierku súborov WebFilter predmety.

Keď v našej aplikácii povolíme funkcie OAuth2, Spring Security pridá do reťazca dva filtre:

  1. Jeden filter reaguje na žiadosti o autorizáciu ( / oauth2 / autorizácia / {registrationId} URI) alebo hodí a ClientAuthorizationRequiredException. Obsahuje odkaz na ReactiveClientRegistrationRepository, a má na starosti vytvorenie žiadosti o autorizáciu na presmerovanie agenta používateľa.
  2. Druhý filter sa líši v závislosti od toho, ktorú funkciu pridávame (možnosti klienta OAuth2 alebo funkcia prihlásenia OAuth2). V obidvoch prípadoch je hlavnou zodpovednosťou tohto filtra vytvorenie OAuth2AuthorizedClient inštanciu a uložte ju pomocou ServerOAuth2AuthorizedClientRepository.

3.6. Webový klient

Webový klient bude nakonfigurovaný s ExchangeFilterFunction obsahujúce odkazy na archívy.

Použije ich na získanie prístupového tokenu na jeho automatické pridanie k žiadosti.

4. Podpora jarnej bezpečnosti 5 - tok poverení klienta

Spring Security umožňuje konfigurovať našu aplikáciu ako klienta OAuth2.

V tomto spise použijeme a Webový klient napríklad na získanie zdrojov pomocou „poverení klienta“najskôr typ grantu a potom použite tok „autorizačného kódu“.

Prvá vec, ktorú musíme urobiť, je nakonfigurovať registráciu klienta a poskytovateľa, ktorého použijeme na získanie prístupového tokenu.

4.1. Konfigurácia klienta a poskytovateľa

Ako sme videli v článku OAuth2 Login, môžeme ho buď nakonfigurovať programovo, alebo sa spoľahnúť na automatickú konfiguráciu Spring Boot pomocou vlastností na definovanie našej registrácie:

spring.security.oauth2.client.registration.bael.authorization-grant-type = client_credentials spring.security.oauth2.client.registration.bael.client-id = bael-client-id spring.security.oauth2.client.registration. bael.client-secret = bael-secret spring.security.oauth2.client.provider.bael.token-uri = // localhost: 8085 / oauth / token

Toto sú všetky konfigurácie, ktoré potrebujeme na získanie zdroja pomocou client_credentials tok.

4.2. Pomocou Webový klient

Tento typ grantu používame v komunikácii medzi strojmi, kde neexistuje interakcia koncového používateľa s našou aplikáciou.

Napríklad si predstavme, že máme a cron úloha, ktorá sa snaží získať zabezpečený zdroj pomocou a Webový klient v našej aplikácii:

@Autowired private WebClient webClient; @Scheduled (fixedRate = 5000) public void logResourceServiceResponse () {webClient.get () .uri ("// localhost: 8084 / retrieve-resource") .retrieve () .bodyToMono (String.class) .map (string -> "Získané pomocou Typu udelenia poverenia klienta:" + reťazec) .subscribe (logger :: info); }

4.3. Konfigurácia Webový klient

Ďalej nastavíme webový klient inštancia, ktorú sme autowired v našej naplánovanej úlohe:

@Bean WebClient webClient (ReactiveClientRegistrationRepository clientRegistrations) {ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = nový ServerOAuth2AuthorizedClientExchangeFilterFunction (clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository) oauth.setDefaultClientRegistrationId ("bael"); vrátiť WebClient.builder () .filter (oauth) .build (); }

Ako sme už povedali, úložisko registrácie klientov sa automaticky vytvorí a do kontextu sa pridá pomocou Spring Boot.

Ďalšia vec, ktorú si tu treba všimnúť, je, že používame a UnAuthenticatedServerOAuth2AuthorizedClientRepository inštancia. Je to tak kvôli skutočnosti, že sa procesu nebude zúčastňovať žiadny koncový používateľ, pretože ide o komunikáciu typu „stroj-stroj“. Nakoniec sme uviedli, že použijeme bael registrácia klienta štandardne.

Inak by sme to museli špecifikovať do času, keď definujeme požiadavku v úlohe cron:

webClient.get () .uri ("// localhost: 8084 / retrieve-resource") .attributes (ServerOAuth2AuthorizedClientExchangeFilterFunction .clientRegistrationId ("bael")) .retrieve () // ...

4.4. Testovanie

Ak spustíme našu aplikáciu s DEBUG úroveň protokolovania povolená, uvidíme hovory, ktoré pre nás robí Spring Security:

oswrfclient.ExchangeFunctions: HTTP POST // localhost: 8085 / oauth / token oshttp.codec.json.Jackson2JsonDecoder: Decoded [{access_token = 89cf72cd-183e-48a8-9d08-661584db4310, token_type = bearer, expires_type = bearer, expires_type = nosič, vyprší read (truncated) ...] oswrfclient.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource oscore.codec.StringDecoder: Decoded "Toto je zdroj!" c.b.w.c.service.WebClientChonJob: Pomocou zdroja grantu Client Credentials sme načítali nasledujúci zdroj: Toto je zdroj!

Tiež si všimneme, že pri druhom spustení úlohy aplikácia požiada o zdroj bez toho, aby najskôr požiadala o token, pretože posledný nevypršal.

5. Podpora zabezpečenia Spring 5 - Implementácia pomocou toku autorizačného kódu

Tento typ grantu sa zvyčajne používa v prípadoch, keď menej dôveryhodné aplikácie tretích strán potrebujú prístup k zdrojom.

5.1. Konfigurácia klienta a poskytovateľa

Aby sme mohli vykonať proces OAuth2 pomocou toku autorizačného kódu, budeme musieť definovať niekoľko ďalších vlastností pre našu registráciu klienta a poskytovateľa:

spring.security.oauth2.client.registration.bael.client-name = bael spring.security.oauth2.client.registration.bael.client-id = bael-client-id spring.security.oauth2.client.registration.bael. client-secret = bael-secret spring.security.oauth2.client.registration.bael .authorization-grant-type = authorization_code spring.security.oauth2.client.registration.bael .redirect-uri = // localhost: 8080 / login / oauth2 / code / bael spring.security.oauth2.client.provider.bael.token-uri = // localhost: 8085 / oauth / token spring.security.oauth2.client.provider.bael .authorization-uri = // localhost: 8085 / oauth / autorizovať spring.security.oauth2.client.provider.bael.user-info-uri = // localhost: 8084 / užívateľ spring.security.oauth2.client.provider.bael.user-name-attribute = meno

Okrem vlastností, ktoré sme použili v predchádzajúcej časti, musíme tentokrát zahrnúť aj:

  • Koncový bod na autentifikáciu na autentifikačnom serveri
  • URL koncového bodu obsahujúceho informácie o používateľovi
  • URL koncového bodu v našej aplikácii, na ktorú bude po autentifikácii presmerovaný užívateľský agent

Pre známych poskytovateľov samozrejme nie je potrebné špecifikovať prvé dva body.

Koncový bod presmerovania vytvára Spring Security automaticky.

V predvolenom nastavení je nakonfigurovaná adresa URL / [action] / oauth2 / code / [registrationId], iba s povoliť a Prihlásiť sa povolené činnosti (aby sa zabránilo nekonečnej slučke).

Tento koncový bod má na starosti:

  • príjem autentifikačného kódu ako parametra dopytu
  • jeho použitie na získanie prístupového tokenu
  • vytvorenie inštancie Autorizovaného klienta
  • presmerovanie agenta užívateľa na pôvodný koncový bod

5.2. Konfigurácie zabezpečenia HTTP

Ďalej budeme musieť nakonfigurovať SecurityWebFilterChain.

Najbežnejším scenárom je použitie schopností Spring Security OAuth2 Login na autentifikáciu používateľov a poskytnutie prístupu k našim koncovým bodom a prostriedkom.

Ak je to náš prípad, potom len vrátane oauth2Login smernica v ServerHttpSecurity Na to, aby naša aplikácia fungovala aj ako klient OAuth2, bude stačiť definícia:

@Bean public SecurityWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) {http.authorizeExchange () .anyExchange () .authenticated (). A () .oauth2Login (); vrátiť http.build (); }

5.3. Konfigurácia Webový klient

Teraz je čas zaviesť naše Webový klient inštancia:

@Bean WebClient webClient (ReactiveClientRegistrationRepository clientRegistrations, ServerOAuth2AuthorizedClientRepository authorizedClients) {ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = nový ServerOAuth2AuthorizedClientExchangeFilterFunction (clientReentsrations, authorizedClients) oauth.setDefaultOAuth2AuthorizedClient (true); vrátiť WebClient.builder () .filter (oauth) .build (); }

Tentokrát vkladáme z registra kontextové úložisko klientov aj autorizované úložisko klientov.

Povoľujeme tiež setDefaultOAuth2AuthorizedClient možnosť. Pomocou neho sa rámec pokúsi získať informácie o klientovi z aktuálneho Overenie objekt spravovaný v jarnej bezpečnosti.

Musíme vziať do úvahy, že s ním budú všetky požiadavky HTTP obsahovať prístupový token, čo nemusí byť požadované správanie.

Neskôr budeme analyzovať alternatívy, aby sme klientovi naznačili, že je konkrétny Webový klient transakcia použije.

5.4. Pomocou Webový klient

Autorizačný kód vyžaduje na vykonanie postupu používateľského agenta, ktorý dokáže vypracovať presmerovania (napr. Prehliadač).

Preto tento typ grantu využívame, keď používateľ interaguje s našou aplikáciou, zvyčajne volá koncový bod HTTP:

@RestController verejná trieda ClientRestController {@Autowired WebClient webClient; @GetMapping ("/ auth-code") Mono useOauthWithAuthCode () {Mono retrievedResource = webClient.get () .uri ("// localhost: 8084 / retrieve-resource") .retrieve () .bodyToMono (String.class); return retrievedResource.map (string -> "Nasledujúci zdroj sme načítali pomocou protokolu Oauth:" + reťazec); }}

5.5. Testovanie

Na záver zavoláme koncový bod a analyzujeme, čo sa deje, a to skontrolovaním položiek protokolu.

Keď zavoláme koncový bod, aplikácia overí, či ešte nie sme v aplikácii overení:

o.s.w.s.adapter.HttpWebHandlerAdapter: HTTP GET "/ auth-code" ... HTTP / 1.1 302 Nájdené umiestnenie: / oauth2 / autorizácia / bael

Aplikácia presmeruje na koncový bod autorizačnej služby na autentifikáciu pomocou poverení existujúcich v registroch poskytovateľa (v našom prípade použijeme bael-user / bael-heslo):

HTTP / 1.1 302 Nájdené umiestnenie: // localhost: 8085 / oauth / authorize? Response_type = code & client_id = bael-client-id & state = ... & redirect_uri = http% 3A% 2F% 2Flocalhost% 3A8080% 2Flogin% 2Foauth2% 2Fcode% 2Fbael

Po autentifikácii sa užívateľský agent odošle späť na URI presmerovania spolu s kódom ako parametrom dotazu a hodnotou stavu, ktorá sa poslala ako prvá (aby sa zabránilo útokom CSRF):

o.s.w.s.adaptér.HttpWebHandlerAdapter: HTTP GET "/ login / oauth2 / code / bael? code = ... & state = ...

Aplikácia potom použije kód na získanie prístupového tokenu:

o.s.w.r.f.client.ExchangeFunctions: HTTP POST // localhost: 8085 / oauth / token

Získava informácie o používateľoch:

o.s.w.r.f.client.ExchangeFunctions: HTTP GET // localhost: 8084 / užívateľ

A presmeruje agenta používateľa na pôvodný koncový bod:

HTTP / 1.1 302 Nájdené umiestnenie: / auth-code

Nakoniec náš Webový klient inštancia môže úspešne požiadať o zabezpečený zdroj:

o.s.w.r.f.client.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource o.s.w.r.f.client.ExchangeFunctions: Odpoveď 200 v poriadku o.s.core.codec.StringDecoder: Decoded "Toto je zdroj!"

6. Alternatíva - Registrácia klienta vo výzve

Predtým sme to videli pomocou setDefaultOAuth2AuthorizedClientznamená, že aplikácia bude obsahovať prístupový token pri každom hovore, ktorý realizujeme s klientom.

Ak tento príkaz odstránime z konfigurácie, budeme musieť do času, keď definujeme požiadavku, výslovne špecifikovať registráciu klienta.

Jedným zo spôsobov, samozrejme, je použitie clientRegistrationId ako sme to robili predtým pri práci v toku prihlasovacích údajov klienta.

Keďže sme združili Principal s oprávnenými klientmi môžeme získať OAuth2AuthorizedClient napríklad pomocou @ RegisteredOAuth2AuthorizedClient anotácia:

@GetMapping ("/ auth-code-annotated") Mono useOauthWithAuthCodeAndAnnotation (@ RegisteredOAuth2AuthorizedClient ("bael") OAuth2AuthorizedClient authorizedClient) {Mono retrievedResource = webClient.get () .uri ("// localhost: 8084). atribúty (ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient (authorizedClient)) .retrieve () .bodyToMono (String.class); return retrievedResource.map (string -> "Resource:" + string + "- Principal associated:" + authorizedClient.getPrincipalName () + "- Platnosť tokenu vyprší o:" + authorizedClient.getAccessToken () .getExpiresAt ()); }

7. Vyhýbanie sa funkciám prihlásenia OAuth2

Ako sme už povedali, najbežnejším scenárom je spoliehanie sa na poskytovateľa autorizácie OAuth2 pri prihlasovaní používateľov do našej aplikácie.

Čo však v prípade, že sa tomu chceme vyhnúť, ale stále budeme mať prístup k zabezpečeným zdrojom pomocou protokolu OAuth2? Potom budeme musieť urobiť nejaké zmeny v našej konfigurácii.

Pre začiatočníkov môžeme použiť povoliť akcia namiesto Prihlásiť sa jeden pri definovaní vlastnosti URI presmerovania:

spring.security.oauth2.client.registration.bael .redirect-uri = // localhost: 8080 / login / oauth2 / code / bael

Môžeme tiež zrušiť vlastnosti súvisiace s používateľom, pretože ich nebudeme používať na vytvorenie súboru Principal v našej aplikácii.

Teraz nakonfigurujeme SecurityWebFilterChain bez zahrnutia oauth2Login príkaz a namiesto toho zahrnieme oauth2Client jeden.

Aj keď sa nechceme spoliehať na prihlásenie OAuth2, stále chceme autentifikovať používateľov pred prístupom k nášmu koncovému bodu. Z tohto dôvodu zahrnieme aj prihlásenie smernica tu:

@Bean public SecurityWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) {http.authorizeExchange () .anyExchange () .authenticated (). A () .oauth2Client (). A () .formLogin (); vrátiť http.build (); }

Poďme teraz spustiť aplikáciu a skontrolovať, čo sa stane, keď použijeme / auth-code-annotated koncový bod.

Najskôr sa budeme musieť prihlásiť do našej aplikácie pomocou prihlasovacieho formulára.

Potom nás aplikácia presmeruje na prihlasovacie údaje autorizačnej služby, aby sme umožnili prístup k našim zdrojom.

Poznámka: Po vykonaní tohto kroku by sme mali byť presmerovaní späť do pôvodného koncového bodu, ktorý sme nazvali. Zdá sa však, že Spring Security presmerováva späť na koreňovú cestu „/“, čo sa zdá byť chybou. Nasledujúce žiadosti po spustení tanca OAuth2 budú úspešné.

V odpovedi koncového bodu vidíme, že autorizovaný klient je tentokrát spojený s principálom s názvom bael-client-id namiesto užívateľ bael, pomenované podľa používateľa nakonfigurovaného v autentifikačnej službe.

8. Podpora jarného rámca - manuálny prístup

Von z krabice, Jar 5 poskytuje iba jednu metódu služby spojenú s OAuth2 na jednoduché pridanie hlavičky nositeľa tokenu k žiadosti. Je to HttpHeaders # setBearerAuth metóda.

Teraz uvidíme príklad na pochopenie toho, čo by bolo potrebné na získanie nášho zabezpečeného zdroja manuálnym vykonaním tanca OAuth2.

Jednoducho povedané, budeme musieť zreťaziť dve požiadavky HTTP: jednu na získanie autentifikačného tokenu z autorizačného servera a druhú na získanie prostriedku pomocou tohto tokenu:

@Autowired klient WebClient; public Mono obtainSecuredResource () {String encodedClientData = Base64Utils.encodeToString ("bael-client-id: bael-secret" .getBytes ()); Mono zdroj = client.post () .uri ("localhost: 8085 / oauth / token") .header ("autorizácia", "základné" + encodedClientData) .body (BodyInserters.fromFormData ("grant_type", "client_credentials")) .retrieve () .bodyToMono (JsonNode.class) .flatMap (tokenResponse -> {String accessTokenValue = tokenResponse.get ("access_token") .textValue (); return client.get () .uri ("localhost: 8084 / retrieve- zdroj ") .headers (h -> h.setBearerAuth (accessTokenValue)) .retrieve () .bodyToMono (String.class);}); návrat resource.map (res -> "Zdroj bol načítaný pomocou manuálneho prístupu:" + res); }

Tento príklad slúži hlavne na to, aby sme pochopili, aké ťažkopádne môže byť využitie požiadavky podľa špecifikácie OAuth2, a zistenie, ako setBearerAuth metóda sa používa.

V scenári z reálneho života sme umožnili spoločnosti Spring Security postarať sa o všetku tvrdú prácu za nás transparentným spôsobom, ako sme to urobili v predchádzajúcich častiach.

9. Záver

V tomto tutoriáli sme videli, ako môžeme nastaviť našu aplikáciu ako klienta OAuth2, a najmä to, ako môžeme nakonfigurovať a používať Webový klient na získanie zabezpečeného zdroja v plne reaktívnom zásobníku.

V neposlednom rade sme analyzovali, ako mechanizmy Spring Security 5 OAuth2 fungujú pod kapotou, aby vyhovovali špecifikácii OAuth2.

Celý príklad je ako vždy k dispozícii na stránkach Github.


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