Vår WebClient og OAuth2-støtte

1. Oversikt

Spring Security 5 gir OAuth2-støtte for Spring Webfluxs ikke-blokkering WebClient klasse.

I denne opplæringen analyserer vi forskjellige tilnærminger for å få tilgang til sikrede ressurser ved hjelp av denne klassen.

Vi tar også en titt under panseret for å forstå hvordan Spring håndterer OAuth2-autorisasjonsprosessen.

2. Sette opp scenariet

I tråd med OAuth2-spesifikasjonen, bortsett fra vår klient - som er vårt fokusemne i denne artikkelen - trenger vi naturlig nok en autorisasjonsserver og en ressursserver.

Vi kan bruke kjente autorisasjonsleverandører som Google eller Github. For å bedre forstå rollen til OAuth2 Client, kan vi også bruke våre egne servere, med en implementering tilgjengelig her. Vi vil ikke vise full konfigurasjon siden det ikke er temaet for denne opplæringen, det er nok å vite at:

  • Autorisasjonsserveren vil være:
    • kjører på havn 8081
    • avslører / oauth / autorisere,/ oauth / token og oauth / sjekk_token endepunkter for å utføre ønsket funksjonalitet
    • konfigurert med eksempelbrukere (f.eks. john/123) og en enkelt OAuth-klient (fooClientIdPassword/hemmelig)
  • Ressursserveren vil bli skilt fra godkjenningsserveren og vil være:
    • kjører på havn 8082
    • serverer en enkel Foo objekt sikret ressurs tilgjengelig med / foos / {id} endepunkt

Merk: det er viktig å forstå at flere vårprosjekter tilbyr forskjellige OAuth-relaterte funksjoner og implementeringer. Vi kan undersøke hva hvert bibliotek tilbyr i denne vårprosjektmatrisen.

De WebClient og all den reaktive Webflux-relaterte funksjonaliteten er en del av Spring Security 5-prosjektet. Derfor bruker vi hovedsakelig dette rammeverket gjennom denne artikkelen.

3. Vårsikkerhet 5 under panseret

For å fullt ut forstå eksemplene som kommer, er det godt å vite hvordan Spring Security administrerer OAuth2-funksjonene internt.

Dette rammeverket tilbyr muligheter for å:

  • stole på en OAuth2-leverandørkonto for å logge inn brukere i applikasjonen
  • konfigurere tjenesten vår som en OAuth2-klient
  • administrere autorisasjonsprosedyrene for oss
  • oppdater tokens automatisk
  • lagre legitimasjonen hvis nødvendig

Noen av de grunnleggende konseptene i Spring Securitys OAuth2-verden er beskrevet i følgende diagram:

3.1. Leverandører

Spring definerer OAuth2-leverandørrollen, ansvarlig for å eksponere OAuth 2.0-beskyttede ressurser.

I vårt eksempel vil vår autentiseringstjeneste være den som tilbyr leverandørens muligheter.

3.2. Klientregistreringer

EN ClientRegistration er en enhet som inneholder all relevant informasjon om en bestemt klient registrert i en OAuth2 (eller en OpenID) leverandør.

I vårt scenario vil det være klienten som er registrert i autentiseringsserveren, identifisert av bael-klient-id id.

3.3. Autoriserte klienter

Når sluttbrukeren (også kalt ressurseieren) gir klienter tillatelse til å få tilgang til ressursene, an OAuth2AuthorizedClient enhet er opprettet.

Det vil være ansvarlig for å knytte tilgangstokener til klientregistreringer og ressurseiere (representert av Rektor gjenstander).

3.4. Datalagre

Videre tilbyr Spring Security også lagerklasser for å få tilgang til enhetene nevnt ovenfor.

Spesielt den ReactiveClientRegistrationRepository og ServerOAuth2AuthorizedClientRepository klasser brukes i reaktive stabler, og de bruker lagring i minnet som standard.

Spring Boot 2.x oppretter bønner av disse lagerklassene og legger dem automatisk til konteksten.

3.5. Sikkerhetsnettfilterkjede

Et av nøkkelbegrepene i Spring Security 5 er det reaktive SecurityWebFilterChain enhet.

Som navnet antyder, representerer det en lenket samling av WebFilter gjenstander.

Når vi aktiverer OAuth2-funksjonene i applikasjonen vår, legger Spring Security til to filtre i kjeden:

  1. Ett filter svarer på autorisasjonsforespørsler ( / oauth2 / autorisasjon / {registrationId} URI) eller kaster a ClientAuthorizationRequiredException. Den inneholder en referanse til ReactiveClientRegistrationRepository, og det har ansvaret for å opprette autorisasjonsforespørselen for å omdirigere brukeragenten.
  2. Det andre filteret varierer avhengig av hvilken funksjon vi legger til (OAuth2-klientfunksjoner eller OAuth2-påloggingsfunksjonalitet). I begge tilfeller er dette filterets hovedansvar å lage OAuth2AuthorizedClient forekomst og lagre den ved hjelp av ServerOAuth2AuthorizedClientRepository.

3.6. Webklient

Nettklienten vil bli konfigurert med en ExchangeFilterFunction inneholder referanser til depotene.

Det vil bruke dem til å skaffe tilgangstokenet for å legge det automatisk til forespørselen.

4. Support for vårsikkerhet 5 - klientlegitimasjonsflyt

Spring Security gjør det mulig å konfigurere applikasjonen vår som en OAuth2-klient.

I denne oppskriften bruker vi en WebClient forekomst for å hente ressurser ved hjelp av 'Client Credentials'tilskuddstype først, og deretter bruke strømmen 'Autorisasjonskode'.

Det første vi må gjøre er å konfigurere klientregistreringen og leverandøren som vi bruker for å få tilgangstoken.

4.1. Klient- og leverandørkonfigurasjoner

Som vi har sett i OAuth2-påloggingsartikkelen, kan vi enten konfigurere den programmatisk eller stole på Spring Boot-autokonfigurasjonen ved å bruke egenskaper til å definere registreringen:

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

Dette er alle konfigurasjonene vi trenger for å hente ressursen ved hjelp av klientinformasjon strømme.

4.2. Bruker WebClient

Vi bruker denne tilskuddstypen i kommunikasjon mellom maskin og maskin der det ikke er noen sluttbruker som interagerer med applikasjonen vår.

La oss for eksempel forestille oss at vi har en cron jobb som prøver å skaffe en sikret ressurs ved hjelp av en WebClient i søknaden vår:

@Autowired privat WebClient webClient; @Scheduled (fixedRate = 5000) public void logResourceServiceResponse () {webClient.get () .uri ("// localhost: 8084 / retrieve-resource"). Retrieve () .bodyToMono (String.class) .map (string -> "Hentet ved hjelp av Client Credentials Grant Type:" + string). Abonner (logger :: info); }

4.3. Konfigurere WebClient

Neste, la oss sette webClient for eksempel at vi har automatisk ledning i vår planlagte oppgave:

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

Som vi sa, opprettes klientregistreringsregisteret automatisk og legges til i konteksten av Spring Boot.

Den neste tingen å legge merke til her er at vi bruker en UnAuthenticatedServerOAuth2AuthorizedClientRepository forekomst. Dette skyldes det faktum at ingen sluttbrukere vil delta i prosessen siden det er en maskin-til-maskin-kommunikasjon. Til slutt uttalte vi at vi ville bruke bael klientregistrering som standard.

Ellers må vi spesifisere det når vi definerer forespørselen i cron-jobben:

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

4.4. Testing

Hvis vi kjører søknaden vår med DEBUG loggningsnivå aktivert, vil vi kunne se samtalene som Spring Security gjør for oss:

oswrfclient.ExchangeFunctions: HTTP POST // localhost: 8085 / oauth / token oshttp.codec.json.Jackson2JsonDecoder: Decoded [{access_token = 89cf72cd-183e-48a8-9d08-661584db4310, token_type = bearer, bærer = les (avkortet) ...] oswrfclient.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource oscore.codec.StringDecoder: Decoded "Dette er ressursen!" c.b.w.c.service.WebClientChonJob: Vi hentet følgende ressurs ved hjelp av Client Credentials Grant Type: Dette er ressursen!

Vi vil også merke at andre gang oppgaven kjøres, ber applikasjonen om ressursen uten å be om et token først siden den siste ikke har utløpt.

5. Vårsikkerhet 5 Støtte - Implementering ved hjelp av autorisasjonskodestrømmen

Denne tilskuddstypen brukes vanligvis i tilfeller der mindre pålitelige tredjepartsapplikasjoner trenger tilgang til ressurser.

5.1. Klient- og leverandørkonfigurasjoner

For å kunne utføre OAuth2-prosessen ved hjelp av autorisasjonskodestrømmen, må vi definere flere egenskaper for vår klientregistrering og leverandøren:

vår.sikkerhet.oauth2.client.registration.bael.client-name = bael spring.security.oauth2.client.registration.bael.client-id = bael-client-id spring.security.oauth2.client.registration.bael. klient-hemmelighet = bael-hemmelig spring.security.oauth2.client.registration.bael .authorization-grant-type = autorisasjonskode 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 / authorize spring.security.oauth2.client.provider.bael.user-info-uri = // localhost: 8084 / user spring.security.oauth2.client.provider.bael.user-name-attribute = name

Bortsett fra egenskapene vi brukte i forrige avsnitt, må vi også inkludere:

  • Et sluttpunkt å autentisere på autentiseringsserveren
  • URL-en til et sluttpunkt som inneholder brukerinformasjon
  • URL-en til et endepunkt i applikasjonen vår som brukeragenten vil bli omdirigert til etter autentisering

For kjente leverandører trenger de to første punktene selvfølgelig ikke spesifiseres.

Viderekoblingssluttpunktet opprettes automatisk av Spring Security.

URL-en som er konfigurert for den er som standard / [action] / oauth2 / code / [registrationId], med bare autorisere og Logg Inn handlinger tillatt (for å unngå en uendelig løkke).

Dette endepunktet har ansvaret for:

  • motta autentiseringskoden som en spørreparameter
  • bruker den for å skaffe et tilgangstoken
  • opprette autorisert klientforekomst
  • omdirigere brukeragenten tilbake til det opprinnelige sluttpunktet

5.2. HTTP-sikkerhetskonfigurasjoner

Deretter må vi konfigurere SecurityWebFilterChain.

Det vanligste scenariet er å bruke Spring Securitys OAuth2-påloggingsfunksjoner for å autentisere brukere og gi dem tilgang til våre sluttpunkter og ressurser.

Hvis det er vårt tilfelle, da bare inkludert oauth2Login direktivet i ServerHttpSecurity definisjon vil være nok til at søknaden vår også fungerer som OAuth2-klient:

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

5.3. Konfigurere WebClient

Nå er det på tide å få på plass vår WebClient forekomst:

@Bean WebClient webClient (ReactiveClientRegistrationRepository clientRegistrations, ServerOAuth2AuthorizedClientRepository authorizedClients) {ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction, clientRistrations; oauth.setDefaultOAuth2AuthorizedClient (true); returner WebClient.builder () .filter (oauth) .build (); }

Denne gangen injiserer vi både klientregistreringsregisteret og det autoriserte klientregisteret fra konteksten.

Vi aktiverer også setDefaultOAuth2AuthorizedClient alternativ. Med det vil rammeverket prøve å få klientinformasjonen fra den nåværende Godkjenning objekt administrert i Spring Security.

Vi må ta i betraktning at med det vil alle HTTP-forespørsler inkludere tilgangstoken, som kanskje ikke er ønsket oppførsel.

Senere vil vi analysere alternativer for å indikere klienten at en spesifikk WebClient transaksjonen vil bruke.

5.4. Bruker WebClient

Autorisasjonskoden krever en brukeragent som kan utarbeide omdirigeringer (f.eks. En nettleser) for å utføre prosedyren.

Derfor bruker vi denne tilskuddstypen når brukeren samhandler med applikasjonen vår, og vanligvis kaller et HTTP-sluttpunkt:

@RestController offentlig klasse ClientRestController {@Autowired WebClient webClient; @GetMapping ("/ auth-code") Mono useOauthWithAuthCode () {Mono retrievedResource = webClient.get () .uri ("// localhost: 8084 / retrieve-resource"). Hent () .bodyToMono (String.class); return retrievedResource.map (string -> "Vi hentet følgende ressurs ved hjelp av Oauth:" + string); }}

5.5. Testing

Til slutt vil vi ringe sluttpunktet og analysere hva som skjer ved å sjekke loggoppføringene.

Etter at vi har ringt sluttpunktet, verifiserer applikasjonen at vi ennå ikke er autentisert i applikasjonen:

o.s.w.s.adapter.HttpWebHandlerAdapter: HTTP GET "/ auth-code" ... HTTP / 1.1 302 Fant sted: / oauth2 / autorisasjon / bael

Søknaden omdirigerer til autorisasjonstjenestens sluttpunkt for å godkjenne ved hjelp av legitimasjon som finnes i leverandørens registre (i vårt tilfelle bruker vi bael-bruker / bael-passord):

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

Etter godkjenning sendes brukeragenten tilbake til U-omdirigering, sammen med koden som en spørringsparameter og tilstandsverdien som først ble sendt (for å unngå CSRF-angrep):

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

Applikasjonen bruker deretter koden for å få et tilgangstoken:

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

Det innhenter brukerinformasjon:

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

Og den omdirigerer brukeragenten til det opprinnelige sluttpunktet:

HTTP / 1.1 302 Fant plassering: / auth-code

Til slutt, vår WebClient forekomst kan be om den sikrede ressursen med hell:

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: Decoded "Dette er ressursen!"

6. Et alternativ - Kunderegistrering i samtalen

Tidligere så vi at bruk av setDefaultOAuth2AuthorizedClientinnebærer at applikasjonen vil inkludere tilgangstoken i ethvert anrop vi utfører med klienten.

Hvis vi fjerner denne kommandoen fra konfigurasjonen, må vi spesifisere klientregistreringen eksplisitt innen vi definerer forespørselen.

En måte er selvfølgelig ved å bruke clientRegistrationId som vi gjorde før når vi jobbet i klientinformasjonen.

Siden vi assosierte Rektor med autoriserte kunder, kan vi få tak i OAuth2AuthorizedClient eksempel bruker @ RegistrertOAuth2AuthorizedClient kommentar:

@GetMapping ("/ auth-code-annotated") Mono useOauthWithAuthCodeAndAnnotation (@ RegisteredOAuth2AuthorizedClient ("bael") OAuth2AuthorizedClient authorisedClient) {Mono retrievedResource = webClient.get () .uri ("// localhost: 8084 / ret. attributter (ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient (autorisertClient)) .hent () .bodyToMono (String.class); return retrievedResource.map (streng -> "Ressurs:" + streng + "- Rektor tilknyttet:" + autorisertClient.getPrincipalName () + "- Token utløper ved:" + autorisertClient.getAccessToken () .getExpiresAt ()); }

7. Unngå OAuth2-påloggingsfunksjonene

Som vi sa, er det vanligste scenariet å stole på at OAuth2-autorisasjonsleverandøren logger inn brukere i applikasjonen vår.

Men hva om vi vil unngå dette, men likevel få tilgang til sikre ressurser ved hjelp av OAuth2-protokollen? Da må vi gjøre noen endringer i konfigurasjonen.

For det første, og bare for å være tydelig over hele linja, kan vi bruke autorisere handling i stedet for Logg Inn en når du definerer URI-egenskapen for omdirigering:

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

Vi kan også slippe de brukerrelaterte egenskapene, siden vi ikke bruker dem til å lage Rektor i søknaden vår.

Nå konfigurerer vi SecurityWebFilterChain uten å inkludere oauth2Login kommando, og i stedet inkluderer vi oauth2Client en.

Selv om vi ikke vil stole på OAuth2-påloggingen, vil vi fortsatt godkjenne brukere før vi får tilgang til sluttpunktet vårt. Av denne grunn inkluderer vi også formLogin direktiv her:

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

La oss nå kjøre applikasjonen og sjekke ut hva som skjer når vi bruker / autokode-merket endepunkt.

Vi må først logge på applikasjonen vår ved hjelp av skjemainnloggingen.

Etterpå vil applikasjonen omdirigere oss til pålogging for autorisasjonstjenesten for å gi tilgang til ressursene våre.

Merk: etter å ha gjort dette, bør vi bli omdirigert tilbake til det opprinnelige endepunktet som vi ringte. Likevel ser Spring Security ut til å omdirigere tilbake til rotstien “/” i stedet, noe som ser ut til å være en feil. Følgende forespørsler etter den som utløser OAuth2-dansen vil kjøre vellykket.

Vi kan se i endepunktssvaret at den autoriserte klienten denne gangen er tilknyttet en rektor som heter bael-klient-id i stedet for bael-bruker, oppkalt etter brukeren konfigurert i godkjenningstjenesten.

8. Vårrammestøtte - Manuell tilnærming

Ut av boksen, Spring 5 gir bare en OAuth2-relatert tjenestemetode for å enkelt legge til en Bearer-tokenhode til forespørselen. Det er HttpHeaders # setBearerAuth metode.

Vi får nå se et eksempel for å forstå hva som skal til for å skaffe den sikrede ressursen vår ved å utføre en OAuth2-dans manuelt.

Enkelt sagt må vi kjede to HTTP-forespørsler: en for å få et godkjenningstoken fra autorisasjonsserveren, og den andre for å skaffe ressursen ved hjelp av dette tokenet:

@Autowired WebClient-klient; offentlig mono fåSecuredResource () {String encodedClientData = Base64Utils.encodeToString ("bael-client-id: bael-secret" .getBytes ()); Mono ressurs = client.post () .uri ("localhost: 8085 / oauth / token"). Header ("Authorization", "Basic" + encodedClientData) .body (BodyInserters.fromFormData ("grant_type", "client_credentials")) .hent () .bodyToMono (JsonNode.class) .flatMap (tokenResponse -> {String accessTokenValue = tokenResponse.get ("access_token") .textValue (); return client.get () .uri ("localhost: 8084 / retrieve-) ressurs ") .headers (h -> h.setBearerAuth (accessTokenValue)) .retrieve () .bodyToMono (String.class);}); return resource.map (res -> "Hentet ressursen ved hjelp av en manuell tilnærming:" + res); }

Dette eksemplet er hovedsakelig for å forstå hvor tungvint det kan være å utnytte en forespørsel etter OAuth2-spesifikasjonen og for å se hvordan setBearerAuth metoden brukes.

I et reelt scenario vil vi la Spring Security ta seg av alt det harde arbeidet for oss på en gjennomsiktig måte, som vi gjorde i tidligere avsnitt.

9. Konklusjon

I denne veiledningen har vi sett hvordan vi kan konfigurere applikasjonen vår som en OAuth2-klient, og nærmere bestemt hvordan vi kan konfigurere og bruke WebClient for å hente en sikret ressurs i en fullreaktiv stabel.

Sist, men ikke minst, har vi analysert hvordan Spring Security 5 OAuth2-mekanismer fungerer under panseret for å overholde OAuth2-spesifikasjonen.

Som alltid er hele eksemplet tilgjengelig på Github.


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