Proljetna podrška za WebClient i OAuth2

1. Pregled

Spring Security 5 pruža OAuth2 podršku za neblokiranje Spring Webfluxa WebClient razred.

U ovom ćemo uputstvu analizirati različite pristupe pristupu zaštićenim resursima pomoću ove klase.

Također, bacit ćemo pogled ispod haube kako bismo razumjeli kako Spring postupa s postupkom autorizacije OAuth2.

2. Postavljanje scenarija

U skladu s OAuth2 specifikacijom, osim našeg klijenta - koji je naša glavna tema u ovom članku - prirodno nam trebaju Autorizacijski poslužitelj i Resursni poslužitelj.

Možemo koristiti poznate pružatelje autorizacija poput Googlea ili Githuba. Da bismo bolje razumjeli ulogu OAuth2 klijenta, možemo koristiti i vlastite poslužitelje, a ovdje je dostupna implementacija. Nećemo prikazati potpunu konfiguraciju jer to nije tema ovog vodiča, dovoljno je znati da:

  • poslužitelj za autorizaciju bit će:
    • trčanje u luci 8081
    • izlažući / oauth / autorizirajte,/ oauth / token i oauth / check_token krajnje točke za izvođenje željene funkcionalnosti
    • konfigurirano s uzorcima korisnika (npr. Ivan/123) i jedan OAuth klijent (fooClientIdPassword/tajna)
  • Resursni poslužitelj bit će odvojen od autentifikacijskog poslužitelja i bit će:
    • trčanje u luci 8082
    • posluživanje jednostavnog Foo objekt osiguran resurs dostupan putem / foos / {id} krajnja točka

Napomena: važno je razumjeti da nekoliko proljetnih projekata nudi različite značajke i implementacije povezane s OAuthom. Možemo ispitati što svaka knjižnica nudi u ovoj matrici Proljetnih projekata.

The WebClient a sve reaktivne funkcionalnosti povezane s Webfluxom dio su projekta Spring Security 5. Stoga ćemo ovaj okvir uglavnom koristiti u ovom članku.

3. Proljetna sigurnost 5 ispod haube

Kako bismo u potpunosti razumjeli primjere koji dolaze, dobro je znati kako Spring Security interno upravlja značajkama OAuth2.

Ovaj okvir nudi mogućnosti za:

  • oslanjati se na račun davatelja usluge OAuth2 za prijavu korisnika u aplikaciju
  • konfigurirajte našu uslugu kao OAuth2 klijenta
  • upravljati postupcima autorizacije za nas
  • automatski osvježite tokene
  • pohranite vjerodajnice ako je potrebno

Neki od temeljnih koncepata OAuth2 svijeta Spring Securitya opisani su u sljedećem dijagramu:

3.1. Pružatelji usluga

Spring definira ulogu pružatelja usluga OAuth2, odgovornog za izlaganje zaštićenih resursa OAuth 2.0.

U našem primjeru, naša usluga autentifikacije ponudit će mogućnosti davatelja usluga.

3.2. Registracije klijenata

A ClientRegistration je entitet koji sadrži sve relevantne informacije određenog klijenta registriranog u OAuth2 (ili OpenID) dobavljaču.

U našem scenariju to će biti klijent registriran na poslužitelju za provjeru autentičnosti, kojeg identificira bael-client-id iskaznica.

3.3. Ovlašteni klijenti

Jednom kada krajnji korisnik (zvani Vlasnik resursa) klijentu odobri pristup svojim resursima, an OAuth2AuthorizedClient entitet je stvoren.

Bit će odgovoran za pridruživanje pristupnih tokena registracijama klijenata i vlasnicima resursa (koje predstavlja Glavni predmeti).

3.4. Spremišta

Nadalje, Spring Security također nudi klase spremišta za pristup gore spomenutim entitetima.

Osobito, ReactiveClientRegistrationRepository i Spremište ServerOAuth2AuthorizedClientRepository klase se koriste u reaktivnim hrpama i prema zadanim postavkama koriste memoriju u memoriji.

Spring Boot 2.x stvara grah ovih klasa spremišta i dodaje ih automatski u kontekst.

3.5. Lanac sigurnosnog web filtra

Jedan od ključnih pojmova u Spring Security 5 je reaktivni SigurnostWebFilterChain entitet.

Kao što mu samo ime govori, predstavlja ulančanu kolekciju WebFilter predmeta.

Kada u našoj aplikaciji omogućimo značajke OAuth2, Spring Security lancu dodaje dva filtra:

  1. Jedan filtar odgovara na zahtjeve za autorizacijom ( / oauth2 / autorizacija / {registrationId} URI) ili baca a ClientAuthorizationRequiredException. Sadrži referencu na ReactiveClientRegistrationRepository, i zadužen je za stvaranje zahtjeva za autorizaciju za preusmjeravanje korisničkog agenta.
  2. Drugi se filtar razlikuje ovisno o tome koju značajku dodajemo (mogućnosti OAuth2 klijenta ili funkcionalnost prijave OAuth2). U oba slučaja, glavna odgovornost ovog filtra je stvoriti OAuth2AuthorizedClient instance i pohranite je pomoću Spremište ServerOAuth2AuthorizedClientRepository.

3.6. Web klijent

Web klijent bit će konfiguriran s ExchangeFilterFunction koji sadrže reference na spremišta.

Upotrijebit će ih za dobivanje pristupnog tokena za automatsko dodavanje u zahtjev.

4. Podrška za Spring Spring 5 - tijek vjerodajnica klijenta

Spring Security omogućuje konfiguriranje naše aplikacije kao OAuth2 klijenta.

U ovom zapisu koristit ćemo a WebClient instancu za dohvaćanje resursa pomoću "vjerodajnica klijenta"prvo dodijelite vrstu, a zatim pomoću toka 'Autorizacijski kod'.

Prvo što moramo učiniti je konfigurirati registraciju klijenta i davatelja usluga koje ćemo koristiti za dobivanje pristupnog tokena.

4.1. Konfiguracije klijenta i davatelja usluga

Kao što smo vidjeli u članku OAuth2 Prijava, možemo ga programski konfigurirati ili se osloniti na automatsku konfiguraciju Spring Boot koristeći svojstva za definiranje naše registracije:

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

To su sve konfiguracije koje su nam potrebne za dohvaćanje resursa pomoću vjerodajnice_klijenta teći.

4.2. Koristiti WebClient

Ovu vrstu odobrenja koristimo u komunikaciji između računala, gdje krajnji korisnik ne komunicira s našom aplikacijom.

Na primjer, zamislimo da imamo cron posao koji pokušava dobiti siguran resurs pomoću a WebClient u našoj aplikaciji:

@Autowired privatni WebClient webClient; @Scheduled (fixedRate = 5000) public void logResourceServiceResponse () {webClient.get () .uri ("// localhost: 8084 / retrieve-resource") .retrieve () .bodyToMono (String.class) .map (string -> "Preuzeto s vrstom vjerodajnica klijenta Vrsta odobrenja:" + niz) .subscribe (logger :: info); }

4.3. Konfiguriranje WebClient

Dalje, postavimo webClient instancu koju smo automatski povezali u naš planirani zadatak:

@Bean WebClient webClient (ReactiveClientRegistrationRepository clientRegistrations) {ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction (clientRegistrations, new UnAuthenticatedServerOOututhorized) oauth.setDefaultClientRegistrationId ("bael"); vratiti WebClient.builder () .filter (oauth) .build (); }

Kao što smo rekli, spremište za registraciju klijenta automatski se stvara i dodaje u kontekst pomoću Spring Boot-a.

Sljedeće što ovdje treba primijetiti jest da koristimo UnAuthenticatedServerOAuth2AuthorizedClientRepository primjer. To je zbog činjenice da niti jedan krajnji korisnik neće sudjelovati u procesu jer se radi o komunikaciji stroj-stroj. Napokon, izjavili smo da ćemo koristiti bael registracija klijenta prema zadanim postavkama.

U suprotnom, morali bismo ga specificirati do trenutka kada definiramo zahtjev u cron poslu:

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

4.4. Testiranje

Ako pokrenemo našu aplikaciju s DEBUG razina zapisivanja omogućena, moći ćemo vidjeti pozive koje Spring Security radi za nas:

oswrfclient.ExchangeFunctions: HTTP POST // localhost: 8085 / oauth / token oshttp.codec.json.Jackson2JsonDecoder: Dekodirano [{access_token = 89cf72cd-183e-48a8-9d08-661584db4310, 41 token_type čitanje (skraćeno) ...] oswrfclient.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource oscore.codec.StringDecoder: Dekodirano "Ovo je resurs!" c.b.w.c.service.WebClientChonJob: Doznali smo sljedeći resurs koristeći tip odobrenja za vjerodajnice klijenta: Ovo je resurs!

Također ćemo primijetiti da drugi put kada se zadatak izvrši, aplikacija zahtijeva resurs bez da prvo traži token, budući da posljednji nije istekao.

5. Podrška za Spring Spring 5 - Implementacija pomoću protoka autorizacijskog koda

Ova vrsta odobrenja obično se koristi u slučajevima kada aplikacije treće strane s manje povjerenja trebaju pristupiti resursima.

5.1. Konfiguracije klijenta i davatelja usluga

Da bismo izvršili OAuth2 postupak koristeći tijek autorizacijskog koda, morat ćemo definirati još nekoliko svojstava za registraciju klijenta i davatelja usluga:

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 = auth_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 / autorizirajte spring.security.oauth2.client.provider.bael.user-info-uri = // localhost: 8084 / user spring.security.oauth2.client.provider.bael.user-name-attribute = name

Osim svojstava koja smo koristili u prethodnom odjeljku, ovaj put trebamo uključiti i:

  • Krajnja točka za provjeru autentičnosti na poslužitelju za provjeru autentičnosti
  • URL krajnje točke koja sadrži korisničke podatke
  • URL krajnje točke u našoj aplikaciji na koju će korisnički agent biti preusmjeren nakon provjere autentičnosti

Naravno, za dobro poznate pružatelje usluga ne treba navesti prve dvije točke.

Krajnju točku preusmjeravanja automatski stvara Spring Security.

Prema zadanim postavkama URL konfiguriran za njega je / [action] / oauth2 / code / [registrationId], sa samo ovlastiti i prijaviti se dopuštene radnje (kako bi se izbjegla beskonačna petlja).

Ova krajnja točka zadužena je za:

  • primanje autentifikacijskog koda kao parametar upita
  • pomoću koje se dobiva pristupni token
  • stvaranje instance ovlaštenog klijenta
  • preusmjeravanje korisničkog agenta natrag na izvornu krajnju točku

5.2. HTTP sigurnosne konfiguracije

Dalje, trebamo konfigurirati SigurnostWebFilterChain.

Najčešći je scenarij korištenje mogućnosti OAuth2 prijave Spring Security za autentifikaciju korisnika i omogućavanje pristupa našim krajnjim točkama i resursima.

Ako je to naš slučaj, onda samo uključujući oauth2Login direktiva u ServerHttpSecurity definicija će biti dovoljna da i naša aplikacija radi kao OAuth2 klijent:

@Bean public SecurityWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) {http.authorizeExchange () .anyExchange () .authenticated () .and () .oauth2Login (); vratiti http.build (); }

5.3. Konfiguriranje WebClient

Sada je vrijeme da postavimo svoje WebClient primjer:

@Bean WebClient webClient (ReactiveClientRegistrationRepository clientRegistrations, ServerOAuth2AuthorizedClientRepository odobreniClients) {ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = novi ServerOAuth2AuthorizedClientExchangeFilterFunctions; oauth.setDefaultOAuth2AuthorizedClient (true); vratiti WebClient.builder () .filter (oauth) .build (); }

Ovaj put ubrizgavamo i spremište registracije klijenta i ovlašteno spremište klijenta iz konteksta.

Omogućujemo i setDefaultOAuth2AuthorizedClient opcija. Pomoću njega će okvir pokušati dobiti podatke o klijentu od trenutnog Ovjera objekt kojim se upravlja u Spring Security.

Moramo uzeti u obzir da će s njim svi HTTP zahtjevi sadržavati token pristupa, što možda nije željeno ponašanje.

Kasnije ćemo analizirati alternative kako bismo nagovijestili klijenta da je određena WebClient transakcija će se koristiti.

5.4. Koristiti WebClient

Kôd za autorizaciju zahtijeva korisničkog agenta koji može izraditi preusmjeravanja (npr. Preglednik) za izvršavanje postupka.

Stoga se koristimo ovom vrstom odobrenja kada korisnik komunicira s našom aplikacijom, obično pozivajući HTTP krajnju točku:

@RestController javna klasa 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 -> "Dohvatili smo sljedeći resurs pomoću Oauth:" + string); }}

5.5. Testiranje

Na kraju ćemo nazvati krajnju točku i analizirati što se događa provjerom unosa u zapisnik.

Nakon što pozovemo krajnju točku, aplikacija potvrđuje da još nismo provjerili autentičnost u aplikaciji:

o.s.w.s.adapter.HttpWebHandlerAdapter: HTTP GET "/ auth-code" ... HTTP / 1.1 302 Pronađeno mjesto: / oauth2 / autorizacija / bael

Aplikacija se preusmjerava na krajnju točku Službe za autorizaciju radi provjere autentičnosti pomoću vjerodajnica koje postoje u registrima Davatelja (u našem ćemo slučaju koristiti korisnik-korisnik / lozinka-lozinka):

HTTP / 1.1 302 Pronađeno mjesto: // localhost: 8085 / oauth / autorizirati? Response_type = code & client_id = bael-client-id & state = ... & redirect_uri = http% 3A% 2F% 2Flocalhost% 3A8080% 2Flogin% 2Foauth2% 2Fcode% 2Fbael

Nakon autentifikacije, korisnički agent šalje se natrag na URI za preusmjeravanje, zajedno s kodom kao parametar upita i vrijednošću stanja koja je prva poslana (kako bi se izbjegli CSRF napadi):

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

Zatim aplikacija koristi kôd za dobivanje pristupnog tokena:

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

Dobiva podatke o korisnicima:

o.s.w.r.f.client.ExchangeFunctions: HTTP GET // localhost: 8084 / user

I preusmjerava korisničkog agenta na izvornu krajnju točku:

HTTP / 1.1 302 Pronađeno mjesto: / auth-code

Napokon, naša WebClient instanca može uspješno zatražiti zaštićeni resurs:

o.s.w.r.f.client.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource o.s.w.r.f.client.ExchangeFunctions: Response 200 OK o.s.core.codec.StringDecoder: Dekodirano "Ovo je resurs!"

6. Alternativa - registracija klijenta u pozivu

Ranije smo vidjeli da se pomoću setDefaultOAuth2AuthorizedClientpodrazumijeva da će aplikacija sadržavati pristupni token u bilo kojem pozivu koji realiziramo s klijentom.

Ako uklonimo ovu naredbu iz konfiguracije, morat ćemo izričito navesti registraciju klijenta do trenutka kada definiramo zahtjev.

Jedan od načina je, naravno, korištenje clientRegistrationId kao što smo to radili i prije dok smo radili u tijeku vjerodajnica klijenta.

Budući da smo povezali Glavni s ovlaštenim klijentima možemo dobiti OAuth2AuthorizedClient primjer pomoću @ RegisteredOAuth2AuthorizedClient napomena:

@GetMapping ("/ auth-code-annotated") Mono useOauthWithAuthCodeAndAnnotation (@ RegisteredOAuth2AuthorizedClient ("bael") OAuth2AuthorizedClient odobrenClient) {Mono retrievedResource = webClient.get (). 80 ("// localhostriest. atributi (ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient (odobreniClient)) .retrieve () .bodyToMono (String.class); vratiti retrievedResource.map (string -> "Resurs:" + string + "- Povezani nalogodavac:" + odobreniClient.getPrincipalName () + "- Token istječe na:" + odobreniClient.getAccessToken () .getExpiresAt ()); }

7. Izbjegavanje značajki prijave OAuth2

Kao što smo rekli, najčešći se scenarij oslanja na pružatelja autorizacije OAuth2 za prijavu korisnika u našu aplikaciju.

Ali što ako to želimo izbjeći, ali još uvijek možemo pristupiti zaštićenim resursima pomoću OAuth2 protokola? Tada ćemo morati unijeti neke promjene u našu konfiguraciju.

Za početak, i samo da budemo jasni, možemo koristiti ovlastiti radnja umjesto prijaviti se jedan prilikom definiranja svojstva preusmjeravanja URI:

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

Također možemo odustati od korisničkih svojstava jer ih nećemo koristiti za stvaranje Glavni u našoj prijavi.

Sada ćemo konfigurirati SigurnostWebFilterChain bez uključivanja oauth2Login naredbu, a umjesto toga ćemo uključiti oauth2Client jedan.

Iako se ne želimo oslanjati na prijavu OAuth2, i dalje želimo provjeriti autentičnost korisnika prije pristupanja našoj krajnjoj točki. Iz tog ćemo razloga uključiti i formLogin direktiva ovdje:

@Bean public SecurityWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) {http.authorizeExchange () .anyExchange () .authenticated () .and () .oauth2Client () .and () .formLogin (); vratiti http.build (); }

Pokrenimo sada aplikaciju i provjerimo što se događa kada koristimo / auth-code-annotated krajnja točka.

Prvo ćemo se morati prijaviti u našu aplikaciju pomoću obrasca za prijavu.

Nakon toga, aplikacija će nas preusmjeriti na prijavu na uslugu autorizacije, kako bi se odobrio pristup našim resursima.

Napomena: nakon što bismo to učinili, trebali bismo biti preusmjereni natrag na izvornu krajnju točku koju smo pozvali. Ipak, čini se da Spring Security preusmjerava natrag na korijensku stazu "/", što je čini se greška. Sljedeći zahtjevi nakon onoga koji pokreće ples OAuth2 izvest će se uspješno.

U odgovoru na krajnju točku možemo vidjeti da je ovlašteni klijent ovog puta povezan s imenovanim nalogodavcem bael-klijent-id umjesto korisnik bael-a, nazvan po korisniku konfiguriranom u Authentication Service.

8. Proljetna podrška za okvir - ručni pristup

Izvan kutije, Spring 5 pruža samo jednu metodu usluge koja se odnosi na OAuth2 za jednostavno dodavanje zaglavlja tonera Bearer u zahtjev. To je HttpHeaders # setBearerAuth metoda.

Sad ćemo vidjeti primjer kako bismo razumjeli što bi trebalo za dobivanje našeg osiguranog resursa ručnim izvođenjem plesa OAuth2.

Jednostavno rečeno, morat ćemo povezati dva HTTP zahtjeva: jedan za dobivanje autentifikacijskog tokena s Autorizacijskog poslužitelja, a drugi za dobivanje resursa pomoću ovog tokena:

@Autowired WebClient klijent; javni Mono dobitiSecuredResource () {String encodedClientData = Base64Utils.encodeToString ("bael-client-id: bael-secret" .getBytes ()); Mono resurs = client.post () .uri ("localhost: 8085 / oauth / token") .header ("Autorizacija", "Basic" + 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- resurs ") .headers (h -> h.setBearerAuth (accessTokenValue)) .retrieve () .bodyToMono (String.class);}); vratiti resource.map (res -> "Dohvaćen resurs ručnim pristupom:" + res); }

Ovaj je primjer uglavnom kako bi se razumjelo koliko nezgodno može biti iskorištavanje zahtjeva koji slijedi OAuth2 specifikaciju i kako bi se vidjelo kako setBearerAuth koristi se metoda.

U scenariju iz stvarnog života, dopustili bismo Spring Securityu da se sav naporan posao pobrine za nas na transparentan način, kao što smo to činili u prethodnim odjeljcima.

9. Zaključak

U ovom uputstvu vidjeli smo kako svoju aplikaciju možemo postaviti kao OAuth2 klijenta, a posebice kako možemo konfigurirati i koristiti WebClient za dohvaćanje osiguranog resursa u potpuno reaktivnom stogu.

I na kraju, ali ne najmanje važno, analizirali smo kako mehanizmi Spring Security 5 OAuth2 djeluju ispod poklopca kako bi bili u skladu s OAuth2 specifikacijom.

Kao i uvijek, cjelovit primjer dostupan je na Githubu.


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