Vårsikkerhet og OpenID Connect

Merk at denne artikkelen er oppdatert til den nye Spring Security OAuth 2.0-stakken. Opplæringen ved å bruke den eldre stakken er fremdeles tilgjengelig.

1. Oversikt

I denne raske opplæringen vil vi fokusere på å sette opp OpenID Connect (OIDC) med Spring Security.

Vi presenterer forskjellige aspekter av denne spesifikasjonen, og så ser vi støtten som Spring Security tilbyr for å implementere den på en OAuth 2.0-klient.

2. Rask introduksjon til OpenID Connect

OpenID Connect er et identitetslag bygget på toppen av OAuth 2.0-protokollen.

Dermed er det veldig viktig å kjenne OAuth 2.0 før du dykker inn i OIDC, spesielt autorisasjonskodestrømmen.

OIDC-spesifikasjonssuiten er omfattende; den inneholder kjernefunksjoner og flere andre valgfrie funksjoner, presentert i forskjellige grupper. De viktigste er:

  • Kjernen: autentisering og bruk av krav for å kommunisere sluttbrukerinformasjon
  • Discovery: bestemmer hvordan en klient dynamisk kan bestemme informasjon om OpenID-leverandører
  • Dynamisk registrering: dikterer hvordan en klient kan registrere seg hos en leverandør
  • Session Management: definerer hvordan du skal administrere OIDC-økter

På toppen av dette skiller dokumentene OAuth 2.0-godkjenningsserverne som tilbyr støtte for denne spesifikasjonen, og refererer til dem som "OpenID-leverandører" (OPs) og OAuth 2.0-klienter som bruker OIDC som Relying Parties (RPs). Vi vil følge denne terminologien i denne artikkelen.

Det er også verdt å vite at en klient kan be om bruk av denne utvidelsen ved å legge til openid omfang i autorisasjonsforespørselen.

Til slutt er et annet aspekt som er nyttig å forstå for denne opplæringen det faktum at OP-ene sender ut sluttbrukerinformasjon som en JWT kalt et "ID-token".

Nå ja, vi er klare til å dykke dypere inn i OIDC-verdenen.

3. Prosjektoppsett

Før vi fokuserer på den faktiske utviklingen, må vi registrere en OAuth 2.o-klient hos OpenID-leverandøren vår.

I dette tilfellet bruker vi Google som OpenID-leverandør. Vi kan følge disse instruksjonene for å registrere klientapplikasjonen vår på plattformen deres. Legg merke til at openid omfang er til stede som standard.

Redirect URI vi satte opp i denne prosessen er et sluttpunkt i vår tjeneste: // localhost: 8081 / login / oauth2 / code / google.

Vi bør innhente en klient-ID og en klienthemmelighet fra denne prosessen.

3.1. Maven-konfigurasjon

Vi begynner med å legge til disse avhengighetene i vår prosjektpom-fil:

 org.springframework.boot spring-boot-starter-oauth2-client 2.2.6.RELEASE 

Startgjenstanden samler alle Spring Security Client-relaterte avhengigheter, inkludert:

  • de vår-sikkerhet-oauth2-klient avhengighet for OAuth 2.0 påloggings- og klientfunksjonalitet
  • JOSE-biblioteket for JWT-støtte

Som vanlig kan vi finne den nyeste versjonen av denne gjenstanden ved hjelp av søkemotoren Maven Central.

4. Grunnleggende konfigurasjon ved hjelp av vårstøvel

For det første starter vi med å konfigurere applikasjonen vår til å bruke klientregistreringen vi nettopp opprettet med Google.

Å bruke Spring Boot gjør dette veldig enkelt, ettersom alt vi trenger å gjøre er å definere to applikasjonsegenskaper:

vår: sikkerhet: oauth2: klient: registrering: google: klient-id: klient-hemmelighet: 

La oss starte applikasjonen vår og prøve å få tilgang til et sluttpunkt nå. Vi får se at vi blir omdirigert til en Google-påloggingsside for vår OAuth 2.0-klient.

Det ser veldig enkelt ut, men det skjer ganske mange ting under panseret her. Deretter vil vi utforske hvordan Spring Security trekker dette av seg.

Tidligere, i vårt WebClient- og OAuth 2-støtteinnlegg, analyserte vi internene om hvordan Spring Security håndterer OAuth 2.0-autorisasjonsservere og -klienter.

Der inne så vi at vi måtte gi tilleggsdata, bortsett fra klient-ID og klienthemmelighet, for å konfigurere en ClientRegistration tilfelle vellykket. Så hvordan fungerer dette?

Svaret er, Google er en kjent leverandør, og derfor tilbyr rammeverket noen forhåndsdefinerte egenskaper for å gjøre ting enklere.

Vi kan ta en titt på disse konfigurasjonene i CommonOAuth2Provider enum.

For Google definerer den oppregnede typen egenskaper som:

  • standardomfangene som skal brukes
  • autorisasjonsendepunktet
  • Token-endepunktet
  • UserInfo-sluttpunktet, som også er en del av OIDC Core-spesifikasjonen

4.1. Få tilgang til brukerinformasjon

Spring Security tilbyr en nyttig representasjon av en bruker Rektor registrert hos en OIDC-leverandør, the OidcUser enhet.

Bortsett fra det grunnleggende OAuth2AuthenticatedPrincipal metoder, tilbyr denne enheten noen nyttige funksjoner:

  • hente ID-tokenverdien og kravene den inneholder
  • innhente kravene fra UserInfo-sluttpunktet
  • generere et aggregat av de to settene

Vi har lett tilgang til denne enheten i en kontroller:

@GetMapping ("/ oidc-principal") offentlig OidcUser getOidcUserPrincipal (@AuthenticationPrincipal OidcUser principal) {retur rektor; }

Eller ved å bruke SecurityContextHolder i en bønne:

Autentiseringsautentisering = SecurityContextHolder.getContext (). GetAuthentication (); if (authentication.getPrincipal () instanceof OidcUser) {OidcUser principal = ((OidcUser) authentication.getPrincipal ()); // ...}

Hvis vi inspiserer rektoren, ser vi mye nyttig informasjon her, som brukerens navn, e-post, profilbilde og sted.

Videre er det viktig å merke seg at Spring legger til myndigheter i rektor basert på omfanget de mottok fra leverandøren, foran "OMFANG_“. For eksempel openid omfang blir et SCOPE_openid gitt autoritet.

Disse myndighetene kan for eksempel brukes til å begrense tilgangen til visse ressurser:

@EnableWebSecurity offentlig klasse MappedAuthorities utvider WebSecurityConfigurerAdapter {beskyttet ugyldig konfigurasjon (HttpSecurity http) {http .authorizeRequests (authorizeRequests -> authorizeRequests .mvcMatchers ("/ my-endpoint") .hasAuthority ("SCOPE_openid.) ; }}

5. OIDC i aksjon

Så langt har vi lært hvordan vi enkelt kan implementere en OIDC-påloggingsløsning ved hjelp av Spring Security

Vi har sett fordelen med å delegere brukeridentifikasjonsprosessen til en OpenID-leverandør, som igjen gir detaljert nyttig informasjon, selv på en skalerbar måte.

Men sannheten er at vi hittil ikke måtte håndtere noe OIDC-spesifikt aspekt. Dette betyr at våren gjør det meste av arbeidet for oss.

Derfor vil vi se hva som skjer bak kulissene for å forstå bedre hvordan denne spesifikasjonen blir omsatt og være i stand til å få mest mulig ut av det.

5.1. Påloggingsprosessen

For å se dette tydelig, la oss aktivere RestTemplate logger for å se forespørslene tjenesten utfører:

logging: level: org.springframework.web.client.RestTemplate: DEBUG

Hvis vi kaller et sikret sluttpunkt nå, ser vi at tjenesten utfører den vanlige OAuth 2.0 autorisasjonskodestrømmen. Det er fordi, som vi sa, denne spesifikasjonen er bygget på toppen av OAuth 2.0. Det er uansett noen forskjeller.

For det første, avhengig av leverandøren vi bruker og omfanget vi har konfigurert, kan vi se at tjenesten ringer til UserInfo-sluttpunktet vi nevnte i begynnelsen.

Nemlig hvis autorisasjonsresponsen henter minst en av profil, e-post, adresse eller telefonen rammeverket vil kalle UserInfo-sluttpunktet for å få ytterligere informasjon.

Selv om alt tyder på at Google skal hente profil og e-post omfang - siden vi bruker dem i autorisasjonsforespørselen - henter OP i stedet sine tilpassede kolleger, //www.googleapis.com/auth/userinfo.email og //www.googleapis.com/auth/userinfo.profile, dermed kaller ikke Spring endepunktet.

Dette betyr at all informasjonen vi får inn er en del av ID-token.

Vi kan tilpasse oss denne oppførselen ved å skape og skaffe vår egen OidcUserService forekomst:

@Configuration offentlig klasse OAuth2LoginSecurityConfig utvider WebSecurityConfigurerAdapter {@Override-beskyttet ugyldig konfigurering (HttpSecurity http) kaster Unntak {Set googleScopes = new HashSet (); googleScopes.add ("//www.googleapis.com/auth/userinfo.email"); googleScopes.add ("//www.googleapis.com/auth/userinfo.profile"); OidcUserService googleUserService = ny OidcUserService (); googleUserService.setAccessibleScopes (googleScopes); http .authorizeRequests (authorizeRequests -> authorizeRequests .anyRequest (). authenticated ()) .oauth2Login (oauthLogin -> oauthLogin .userInfoEndpoint () .oidcUserService (googleUserService)); }}

Den andre forskjellen vi vil observere, er en samtale til JWK Set URI. Som vi forklarte i vårt JWS- og JWK-innlegg, brukes dette til å verifisere den JWT-formaterte ID-token-signaturen.

Deretter analyserer vi ID-token i detalj.

5.2. ID-tegnet

Naturligvis dekker OIDC-spesifikasjonen og tilpasser seg mange forskjellige scenarier. I dette tilfellet bruker vi autorisasjonskodestrømmen, og protokollen indikerer at både tilgangstoken og ID-token blir hentet som en del av Token-endepunktsresponsen.

Som vi sa før, OidcUser enheten inneholder kravene i ID-token, og det faktiske JWT-formaterte tokenet, som kan inspiseres ved hjelp av jwt.io.

På toppen av dette tilbyr Spring mange praktiske getters for å oppnå standardkrav definert av spesifikasjonen på en ren måte.

Vi kan se at ID-token inneholder noen obligatoriske krav:

  • utstederidentifikatoren formatert som en URL (f.eks. “//accounts.google.com“)
  • et emne-ID, som er en referanse til sluttbrukeren som utstederen inneholder
  • utløpstiden for token
  • tidspunktet da token ble utstedt
  • publikum, som inneholder OAuth 2.0-klient-IDen vi har konfigurert

Og også mange OIDC-standardkrav som de vi nevnte tidligere (Navn, lokal, bilde, e-post).

Siden disse er standard, kan vi forvente at mange leverandører henter i det minste noen av disse feltene, og derfor letter utviklingen av enklere løsninger.

5.3. Krav og omfang

Som vi kan forestille oss, samsvarer påstandene som hentes av OP med de omfangene vi (eller Spring Security) konfigurerte.

OIDC definerer noen omfang som kan brukes til å be om kravene definert av OIDC:

  • profil, som kan brukes til å be om standardprofilkrav (f.eks. navn, foretrukket_brukernavn,bilde, og så videre)
  • e-post, for å få tilgang til e-post og e-postbekreftet Påstander
  • adresse
  • telefonen, for å be om telefonnummer og telefonnummer_verifisert Påstander

Selv om Spring ikke støtter det ennå, tillater spesifikasjonen å be om enkeltkrav ved å spesifisere dem i autorisasjonsforespørselen.

6. Vårstøtte for OIDC Discovery

Som vi forklarte i innledningen, inneholder OIDC mange forskjellige funksjoner bortsett fra hovedformålet.

Funksjonene vi skal analysere i denne delen og det følgende er valgfrie i OIDC. Derfor er det viktig å forstå at det kan være OP-er som ikke støtter dem.

Spesifikasjonen definerer en Discovery-mekanisme for en RP for å oppdage OP og få informasjon som er nødvendig for å samhandle med den.

I et nøtteskall gir OP-er et JSON-dokument med standard metadata. Informasjonen må betjenes av et kjent endepunkt for utstederens beliggenhet, /.well-known/openid-configuration.

Spring drar nytte av dette ved å la oss konfigurere en ClientRegistration med bare en enkel eiendom, utsteders beliggenhet.

Men la oss hoppe rett inn i et eksempel for å se dette tydelig.

Vi definerer en skikk ClientRegistration forekomst:

vår: sikkerhet: oauth2: klient: registrering: tilpasset-google: klient-id: klient-hemmelig: leverandør: tilpasset-google: utsteder-uri: //accounts.google.com

Nå kan vi starte applikasjonen på nytt og sjekke loggene for å bekrefte at applikasjonen ringer til openid-konfigurasjon endepunkt i oppstartsprosessen.

Vi kan til og med bla gjennom dette endepunktet for å se på informasjonen fra Google:

//accounts.google.com/.well-known/openid-configuration

Vi kan for eksempel se autorisasjons-, token- og UserInfo-endepunktene som tjenesten må bruke, og de støttede omfangene.

Et spesielt relevant notat her er det faktum at hvis Discovery-endepunktet ikke er tilgjengelig når tjenesten startes, vil ikke appen vår kunne fullføre oppstartsprosessen.

7. OpenID Connect Session Management

Denne spesifikasjonen utfyller kjernefunksjonaliteten ved å definere:

  • forskjellige måter å overvåke sluttbrukerens påloggingsstatus på OP fortløpende, slik at RP kan logge ut en sluttbruker som har logget av OpenID-leverandøren
  • muligheten for å registrere RP-avloggings-URIer med OP som en del av klientregistreringen, for å bli varslet når sluttbrukeren logger ut av OP
  • en mekanisme for å varsle OP om at sluttbrukeren har logget ut av nettstedet og kanskje også vil logge av OP

Naturligvis støtter ikke alle OP-er alle disse elementene, og noen av disse løsningene kan bare implementeres i en frontend-implementering via User-Agent.

I denne opplæringen vil vi fokusere på funksjonene som tilbys av Spring for det siste elementet på listen, RP-initiert Logout.

På dette punktet, hvis vi logger på applikasjonen vår, har vi normalt tilgang til hvert sluttpunkt.

Hvis vi logger av (ringer til /Logg ut endepunkt) og vi ber om en sikret ressurs etterpå, vi ser at vi kan få svaret uten å måtte logge på igjen.

Dette stemmer imidlertid ikke; Hvis vi inspiserer nettverk-fanen i feilsøkingskonsollen i nettleseren, ser vi at når vi treffer det sikrede endepunktet andre gang vi blir omdirigert til OP-autorisasjonsendepunktet, og siden vi fremdeles er logget inn der, er strømmen fullført , havner i det sikrede endepunktet nesten umiddelbart.

Selvfølgelig er dette kanskje ikke ønsket oppførsel i noen tilfeller. La oss se hvordan vi kan implementere denne OIDC-mekanismen for å håndtere dette.

7.1. OpenID-leverandørkonfigurasjonen

I dette tilfellet konfigurerer og bruker vi en Okta-forekomst som OpenID-leverandør. Vi vil ikke gå i detaljer om hvordan du oppretter forekomsten, men vi kan følge trinnene i denne veiledningen, og husk at Spring Securitys standard tilbakeringingsendepunkt vil være / login / oauth2 / code / okta.

I vår applikasjon kan vi definere klientregistreringsdataene med egenskaper:

vår: sikkerhet: oauth2: klient: registrering: okta: klient-id: klienthemmelig: leverandør: okta: utsteder-uri: //dev-123.okta.com

OIDC indikerer at OP-logout-sluttpunktet kan spesifiseres i Discovery-dokumentet, som end_session_endpoint element.

7.2. De LogoutSuccessHandler Konfigurasjon

Deretter må vi konfigurere HttpSikkerhet logout logikk ved å tilby en tilpasset LogoutSuccessHandler forekomst:

@ Override beskyttet ugyldig konfigurasjon (HttpSecurity http) kaster Unntak {http .authorizeRequests (authorizeRequests -> authorizeRequests .mvcMatchers ("/ home"). PermitAll () .anyRequest (). Authenticated ()) .oauth2Login (oauthLogin -> oauthLogin ()) .logout (logout -> logout .logoutSuccessHandler (oidcLogoutSuccessHandler ())); }

La oss nå se hvordan vi kan lage en LogoutSuccessHandler for dette formålet ved hjelp av en spesiell klasse levert av Spring Security, the OidcClientInitiatedLogoutSuccessHandler:

@Autowired privat ClientRegistrationRepository clientRegistrationRepository; private LogoutSuccessHandler oidcLogoutSuccessHandler () {OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler (this.clientRegistrationRepository); oidcLogoutSuccessHandler.setPostLogoutRedirectUri (URI.create ("// localhost: 8081 / home")); returner oidcLogoutSuccessHandler; }

Derfor må vi konfigurere denne URI-en som en gyldig avloggings-omdirigerings-URI i OP-klientkonfigurasjonspanelet.

Det er klart at OP-avloggingskonfigurasjonen er inneholdt i klientregistreringsoppsettet, siden alt vi bruker for å konfigurere behandleren er ClientRegistrationRepository bønne til stede i sammenhengen.

Så, hva vil skje nå?

Etter at vi har logget på søknaden vår, kan vi sende en forespørsel til /Logg ut endepunkt levert av Spring Security.

Hvis vi sjekker nettverksloggene i feilsøkingskonsollen i nettleseren, vi får se at vi ble omdirigert til et OP-logout-endepunkt før vi endelig får tilgang til Redirect URI vi konfigurerte.

Neste gang vi får tilgang til et endepunkt i applikasjonen vår som krever godkjenning, må vi obligatorisk logge på igjen i OP-plattformen vår for å få tillatelser.

8. Konklusjon

For å oppsummere, i denne opplæringen lærte vi mye om løsningene som tilbys av OpenID Connect, og hvordan vi kan implementere noen av dem ved hjelp av Spring Security.

Som alltid kan alle de komplette eksemplene finnes i GitHub-repoen.


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