Spring WebClient og OAuth2 Support

1. Oversigt

Spring Security 5 yder OAuth2-support til Spring Webflux's ikke-blokering Webklient klasse.

I denne vejledning analyserer vi forskellige tilgange til adgang til sikrede ressourcer ved hjælp af denne klasse.

Vi kigger også under hætten for at forstå, hvordan Spring håndterer OAuth2-godkendelsesprocessen.

2. Opsætning af scenariet

Inline med OAuth2-specifikationen, bortset fra vores klient - som er vores fokusemne i denne artikel - har vi naturligvis brug for en autorisationsserver og en ressource-server.

Vi kan bruge kendte autorisationsudbydere som Google eller Github. For bedre at forstå OAuth2-klientens rolle kan vi også bruge vores egne servere med en implementering tilgængelig herinde. Vi viser ikke den fulde konfiguration, da det ikke er emnet for denne vejledning, det er nok at vide, at:

  • Autorisationsserveren vil være:
    • kører på havn 8081
    • udsætter / oauth / autorisere,/ oauth / token og oauth / check_token slutpunkter for at udføre den ønskede funktionalitet
    • konfigureret med prøvebrugere (f.eks. John/123) og en enkelt OAuth-klient (fooClientIdPassword/hemmelighed)
  • Ressursserveren adskilles fra godkendelsesserveren og vil være:
    • kører på havn 8082
    • serverer en simpel Foo objekt sikret ressource tilgængelig ved hjælp af / foos / {id} slutpunkt

Bemærk: det er vigtigt at forstå, at flere forårsprojekter tilbyder forskellige OAuth-relaterede funktioner og implementeringer. Vi kan undersøge, hvad hvert bibliotek leverer i denne forårsprojektmatrix.

Det Webklient og al den reaktive Webflux-relaterede funktionalitet er en del af Spring Security 5-projektet. Derfor bruger vi hovedsageligt denne ramme gennem denne artikel.

3. Forårsikkerhed 5 under emhætten

For fuldt ud at forstå de eksempler, der kommer fremad, er det godt at vide, hvordan Spring Security administrerer OAuth2-funktionerne internt.

Denne ramme tilbyder muligheder for at:

  • stole på en OAuth2-udbyderkonto for at logge brugere ind i applikationen
  • konfigurere vores service som en OAuth2-klient
  • administrere godkendelsesprocedurerne for os
  • opdater tokens automatisk
  • opbevar legitimationsoplysningerne, hvis det er nødvendigt

Nogle af de grundlæggende begreber i Spring Securitys OAuth2-verden er beskrevet i følgende diagram:

3.1. Udbydere

Spring definerer OAuth2-udbyderrollen, der er ansvarlig for at udsætte OAuth 2.0-beskyttede ressourcer.

I vores eksempel vil vores godkendelsestjeneste være den, der tilbyder udbyderfunktionerne.

3.2. Klientregistreringer

EN ClientRegistration er en enhed, der indeholder alle relevante oplysninger om en bestemt klient, der er registreret i en OAuth2 (eller en OpenID) udbyder.

I vores scenarie er det klienten, der er registreret i godkendelsesserveren, identificeret af bael-klient-id id.

3.3. Autoriserede klienter

Når slutbrugeren (også kaldet ressourceejeren) giver klienten tilladelse til at få adgang til sine ressourcer, an OAuth2AuthorizedClient enhed oprettes.

Det er ansvarligt for at knytte adgangstokener til klientregistreringer og ressourceejere (repræsenteret af Rektor genstande).

3.4. Opbevaringssteder

Desuden tilbyder Spring Security også lagerklasser for at få adgang til de ovennævnte enheder.

Især den ReactiveClientRegistrationRepository og ServerOAuth2AuthorizedClientRepository klasser bruges i reaktive stakke, og de bruger standardhukommelsen.

Spring Boot 2.x opretter bønner af disse lagerklasser og føjer dem automatisk til konteksten.

3.5. Sikkerhedswebfilterkæde

Et af nøglebegreberne i Spring Security 5 er det reaktive SecurityWebFilterChain enhed.

Som navnet antyder, repræsenterer det en lænket samling af WebFilter genstande.

Når vi aktiverer OAuth2-funktionerne i vores applikation, tilføjer Spring Security to filtre til kæden:

  1. Et filter reagerer på godkendelsesanmodninger ( / oauth2 / autorisation / {registrationId} URI) eller kaster en ClientAuthorizationRequiredException. Den indeholder en henvisning til ReactiveClientRegistrationRepository, og det har ansvaret for at oprette autorisationsanmodningen om at omdirigere brugeragenten.
  2. Det andet filter varierer afhængigt af hvilken funktion vi tilføjer (OAuth2-klientfunktioner eller OAuth2-loginfunktionalitet). I begge tilfælde er dette filters hovedansvar for at oprette OAuth2AuthorizedClient eksempel, og gem det ved hjælp af ServerOAuth2AuthorizedClientRepository.

3.6. Webklient

Webklienten konfigureres med en ExchangeFilterFunction indeholdende henvisninger til arkiverne.

Det bruger dem til at få adgangstokenet til automatisk at føje det til anmodningen.

4. Spring Security 5 Support - klientens legitimationsoplysninger

Spring Security tillader konfiguration af vores applikation som en OAuth2-klient.

I denne opskrivning bruger vi en Webklient instans for at hente ressourcer ved hjælp af 'Client Credentials'tilskudstype først og derefter bruge flowet 'Autorisationskode'.

Den første ting, vi bliver nødt til at gøre, er at konfigurere klientregistreringen og den udbyder, som vi bruger til at få adgangstokenet.

4.1. Klient- og udbyderkonfigurationer

Som vi har set i OAuth2 Login-artiklen, kan vi enten konfigurere den programmatisk eller stole på Spring Boot-auto-konfiguration ved hjælp af egenskaber til at definere vores registrering:

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 de konfigurationer, som vi har brug for for at hente ressourcen ved hjælp af klientoplysninger flyde.

4.2. Bruger Webklient

Vi bruger denne tilskudstype i maskine til maskinkommunikation, hvor der ikke er nogen slutbruger, der interagerer med vores applikation.

Lad os for eksempel forestille os, at vi har en cron job forsøger at skaffe en sikret ressource ved hjælp af en Webklient i vores ansøgning:

@Autowired privat WebClient webClient; @ Planlagt (fixedRate = 5000) offentlig ugyldig logResourceServiceResponse () {webClient.get () .uri ("// localhost: 8084 / retrieve-resource") .hent () .bodyToMono (String.class) .map (streng -> "Hentet ved hjælp af Client Credentials Grant Type:" + string). Abonner (logger :: info); }

4.3. Konfiguration af Webklient

Lad os derefter indstille webClient eksempel, at vi har automatisk kablet i vores planlagte opgave:

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

Som vi sagde, oprettes klientregistreringsregistret automatisk og føjes til konteksten af ​​Spring Boot.

Den næste ting at bemærke her er, at vi bruger en UnAuthenticatedServerOAuth2AuthorizedClientRepository eksempel. Dette skyldes det faktum, at ingen slutbruger deltager i processen, da det er en maskine-til-maskinkommunikation. Endelig sagde vi, at vi ville bruge bael klientregistrering som standard.

Ellers er vi nødt til at specificere det, når vi definerer anmodningen i cron-jobbet:

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

4.4. Testning

Hvis vi kører vores ansøgning med FEJLFINDE logningsniveau aktiveret, kan vi se de opkald, som Spring Security foretager for os:

oswrfclient.ExchangeFunctions: HTTP POST // localhost: 8085 / oauth / token oshttp.codec.json.Jackson2JsonDecoder: Afkodet [{access_token = 89cf72cd-183e-48a8-9d08-661584db4310, token_type = bearer = bærer læs (afkortet) ...] oswrfclient.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource oscore.codec.StringDecoder: Dekodet "Dette er ressourcen!" c.b.w.c.service.WebClientChonJob: Vi hentede følgende ressource ved hjælp af Client Credentials Grant Type: Dette er ressourcen!

Vi bemærker også, at anden gang opgaven kører, anmoder applikationen om ressourcen uden at bede om et token først, da den sidste ikke er udløbet.

5. Spring Security 5 Support - Implementering ved hjælp af flowet til autorisationskoden

Denne tilskudstype bruges normalt i tilfælde, hvor mindre tillid til tredjepartsapplikationer har brug for adgang til ressourcer.

5.1. Klient- og udbyderkonfigurationer

For at udføre OAuth2-processen ved hjælp af flowet med autorisationskode skal vi definere flere egenskaber til vores klientregistrering og udbyderen:

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

Bortset fra egenskaberne, vi brugte i det foregående afsnit, skal vi denne gang også medtage:

  • Et slutpunkt, der skal godkendes på godkendelsesserveren
  • URL'en til et slutpunkt, der indeholder brugeroplysninger
  • URL'en til et slutpunkt i vores applikation, hvortil brugeragenten omdirigeres, efter godkendelse

For velkendte udbydere behøver de to første punkter naturligvis ikke specificeres.

Omdirigering slutpunkt oprettes automatisk af Spring Security.

Den URL, der er konfigureret til den er som standard / [handling] / oauth2 / kode / [registreringsId], med kun bemyndige og Log på handlinger tilladt (for at undgå en uendelig løkke).

Dette slutpunkt er ansvarlig for:

  • modtager godkendelseskoden som en forespørgselsparameter
  • bruger det til at opnå et adgangstoken
  • oprettelse af den autoriserede klientforekomst
  • omdirigere brugeragenten tilbage til det oprindelige slutpunkt

5.2. HTTP-sikkerhedskonfigurationer

Derefter skal vi konfigurere SecurityWebFilterChain.

Det mest almindelige scenario er at bruge Spring Securitys OAuth2-loginfunktioner til at godkende brugere og give dem adgang til vores slutpunkter og ressourcer.

Hvis det er vores sag, så bare inklusive oauth2Login direktivet i ServerHttpSikkerhed definition vil være nok til, at vores applikation også fungerer som en OAuth2-klient:

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

5.3. Konfiguration af Webklient

Nu er det tid til at få vores Webklient eksempel:

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

Denne gang indsprøjter vi både klientregistreringsregistret og det autoriserede klientregister fra konteksten.

Vi muliggør også setDefaultOAuth2AuthorizedClient mulighed. Med det vil rammen forsøge at få klientoplysningerne fra den aktuelle Godkendelse objekt administreret i Spring Security.

Vi er nødt til at tage højde for, at alle HTTP-anmodninger med det inkluderer adgangstoken, hvilket muligvis ikke er den ønskede adfærd.

Senere analyserer vi alternativer for at indikere klienten, at en bestemt Webklient transaktion vil bruge.

5.4. Bruger Webklient

Autorisationskoden kræver en brugeragent, der kan udarbejde omdirigeringer (f.eks. En browser) for at udføre proceduren.

Derfor bruger vi denne tilskudstype, når brugeren interagerer med vores applikation, normalt kalder et HTTP-slutpunkt:

@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); returner retrievedResource.map (streng -> "Vi hentede følgende ressource ved hjælp af Oauth:" + streng); }}

5.5. Testning

Endelig kalder vi slutpunktet og analyserer, hvad der foregår ved at kontrollere logposterne.

Når vi kalder slutpunktet, verificerer applikationen, at vi endnu ikke er godkendt i applikationen:

o.s.w.s.adapter.HttpWebHandlerAdapter: HTTP GET "/ auth-code" ... HTTP / 1.1 302 Fundet placering: / oauth2 / autorisation / bael

Applikationen omdirigerer til autorisationstjenestens slutpunkt for at godkende ved hjælp af legitimationsoplysninger, der findes i udbyderens registre (i vores tilfælde bruger vi bael-bruger / bael-adgangskode):

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

Efter godkendelse sendes brugeragenten tilbage til Redirect URI sammen med koden som en forespørgselsparameter og den tilstandsværdi, der først blev sendt (for at undgå CSRF-angreb):

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

Applikationen bruger derefter koden til at opnå et adgangstoken:

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

Det indhenter brugeroplysninger:

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

Og det omdirigerer brugeragenten til det oprindelige slutpunkt:

HTTP / 1.1 302 fundet placering: / auth-code

Endelig vores Webklient instans kan anmode om den sikrede ressource med succes:

o.s.w.r.f.client.ExchangeFunctions: HTTP GET // localhost: 8084 / retrieve-resource o.s.w.r.f.client.ExchangeFunctions: Svar 200 OK o.s.core.codec.StringDecoder: Dekodet "Dette er ressourcen!"

6. Et alternativ - Kunderegistrering i opkaldet

Tidligere så vi, at brug af setDefaultOAuth2AuthorizedClientindebærer, at applikationen inkluderer adgangstoken i ethvert opkald, vi indfører med klienten.

Hvis vi fjerner denne kommando fra konfigurationen, bliver vi nødt til at specificere klientregistreringen eksplicit, når vi definerer anmodningen.

En måde er selvfølgelig ved at bruge clientRegistrationId som vi gjorde før, da vi arbejdede i klientens legitimationsoplysninger.

Da vi forbandt Rektor med autoriserede kunder kan vi få OAuth2AuthorizedClient eksempel ved hjælp af @ RegistreretOAuth2AuthorizedClient kommentar:

@GetMapping ("/ auth-code-annotated") Mono useOauthWithAuthCodeAndAnnotation (@ RegisteredOAuth2AuthorizedClient ("bael") OAuth2AuthorizedClient authorisedClient) {Mono retrievedResource = webClient.get () .uri ("// localhost: 8084 / ret. Ressource). 8084 / attributter (ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient (autoriseretClient)) .hent () .bodyToMono (String.class); returner retrievedResource.map (streng -> "Ressource:" + streng + "- Principal tilknyttet:" + autoriseretClient.getPrincipalName () + "- Token udløber ved:" + autoriseretClient.getAccessToken () .getExpiresAt ()); }

7. Undgå OAuth2-loginfunktioner

Som vi sagde, er det mest almindelige scenario at stole på, at OAuth2-autorisationsudbyderen logger ind brugere i vores applikation.

Men hvad hvis vi vil undgå dette, men stadig være i stand til at få adgang til sikre ressourcer ved hjælp af OAuth2-protokollen? Så bliver vi nødt til at foretage nogle ændringer i vores konfiguration.

Til at begynde med, og bare for at være klar over hele linjen, kan vi bruge bemyndige handling i stedet for Log på en, når man definerer URI-egenskaben for omdirigering:

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

Vi kan også droppe de brugerrelaterede egenskaber, da vi ikke bruger dem til at oprette Rektor i vores ansøgning.

Nu konfigurerer vi SecurityWebFilterChain uden at inkludere oauth2Login kommando, og i stedet inkluderer vi oauth2Client en.

Selvom vi ikke vil stole på OAuth2-login, vil vi stadig godkende brugere, før vi får adgang til vores slutpunkt. Af denne grund inkluderer vi også formLogin direktiv her:

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

Lad os nu køre applikationen og se, hvad der sker, når vi bruger / auth-code-annotated slutpunkt.

Vi bliver først nødt til at logge ind på vores ansøgning ved hjælp af formular login.

Derefter omdirigerer applikationen os til autorisationstjenestens login for at give adgang til vores ressourcer.

Bemærk: efter at have gjort dette, skal vi omdirigeres tilbage til det oprindelige slutpunkt, som vi ringede til. Ikke desto mindre synes Spring Security at omdirigere tilbage til rodstien “/” i stedet, hvilket synes at være en fejl. Følgende anmodninger efter den, der udløser OAuth2-dansen, kører med succes.

Vi kan se i slutpunktssvaret, at den autoriserede klient denne gang er tilknyttet en navngivet princip bael-klient-id i stedet for bael-bruger, opkaldt efter den bruger, der er konfigureret i godkendelsestjenesten.

8. Spring Framework Support - Manuel tilgang

Ud af boksen, Spring 5 giver kun en OAuth2-relateret servicemetode til nemt at tilføje en Bearer-token-header til anmodningen. Det er HttpHeaders # setBearerAuth metode.

Vi ser nu et eksempel for at forstå, hvad der kræves for at få vores sikrede ressource ved at udføre en OAuth2-dans manuelt.

Kort sagt, vi bliver nødt til at kæde to HTTP-anmodninger: den ene for at få et godkendelsestoken fra autorisationsserveren og den anden for at hente ressourcen ved hjælp af dette token:

@Autowired WebClient-klient; offentlig mono fåSecuredResource () {String encodedClientData = Base64Utils.encodeToString ("bael-client-id: bael-secret" .getBytes ()); Mono ressource = 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-) ressource "). headers (h -> h.setBearerAuth (accessTokenValue)). hent () .bodyToMono (String.class);}); return resource.map (res -> "Hentet ressourcen ved hjælp af en manuel tilgang:" + res); }

Dette eksempel er hovedsageligt for at forstå, hvor besværligt det kan være at udnytte en anmodning efter OAuth2-specifikationen og for at se, hvordan setBearerAuth metode anvendes.

I et virkeligt scenarie ville vi lade Spring Security tage sig af alt det hårde arbejde for os på en gennemsigtig måde, som vi gjorde i tidligere afsnit.

9. Konklusion

I denne vejledning har vi set, hvordan vi kan konfigurere vores applikation som en OAuth2-klient, og mere specifikt, hvordan vi kan konfigurere og bruge Webklient for at hente en sikret ressource i en fuldreaktiv stak.

Sidst men ikke mindst har vi analyseret, hvordan Spring Security 5 OAuth2-mekanismer fungerer under emhætten for at overholde OAuth2-specifikationen.

Som altid er det fulde eksempel tilgængeligt på Github.


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