Spring Security og OpenID Connect

Bemærk, at denne artikel er blevet opdateret til den nye Spring Security OAuth 2.0-stak. Selvstudiet, der bruger den ældre stak, er dog stadig tilgængelig.

1. Oversigt

I denne hurtige vejledning fokuserer vi på opsætning af OpenID Connect (OIDC) med Spring Security.

Vi præsenterer forskellige aspekter af denne specifikation, og så ser vi den support, som Spring Security tilbyder til at implementere den på en OAuth 2.0-klient.

2. Introduktion til hurtig OpenID Connect

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

Således er det virkelig vigtigt at kende OAuth 2.0 før du dykker ned i OIDC, især autorisationskoden.

OIDC-specifikationspakken er omfattende; det inkluderer kernefunktioner og flere andre valgfrie funktioner, præsenteret i forskellige grupper. De vigtigste er:

  • Kerne: godkendelse og brug af krav til kommunikation af slutbrugeroplysninger
  • Opdagelse: fastlægger, hvordan en klient dynamisk kan bestemme oplysninger om OpenID-udbydere
  • Dynamisk registrering: dikterer, hvordan en klient kan registrere sig hos en udbyder
  • Session Management: definerer, hvordan man styrer OIDC-sessioner

Oven i dette skelner dokumenterne mellem OAuth 2.0-godkendelsesservere, der tilbyder support til denne spec, idet de henviser til dem som "OpenID-udbydere" (OP'er) og OAuth 2.0-klienter, der bruger OIDC som Relying Parties (RPs). Vi følger denne terminologi i denne artikel.

Det er også værd at vide, at en klient kan anmode om brugen af ​​denne udvidelse ved at tilføje åben rækkevidde i sin godkendelsesanmodning.

Endelig er et andet aspekt, der er nyttigt at forstå for denne vejledning, det faktum, at OP'erne udsender slutbrugeroplysninger som en JWT kaldet et "ID-token".

Nu ja, vi er klar til at dykke dybere ned i OIDC-verdenen.

3. Opsætning af projekt

Før vi fokuserer på den aktuelle udvikling, bliver vi nødt til at registrere en OAuth 2.o-klient hos vores OpenID-udbyder.

I dette tilfælde bruger vi Google som OpenID-udbyder. Vi kan følge disse instruktioner for at registrere vores klientapplikation på deres platform. Bemærk, at åben omfang er til stede som standard.

Den omdirigerings-URI, vi oprettede i denne proces, er et slutpunkt i vores service: // localhost: 8081 / login / oauth2 / code / google.

Vi skal få en klient-id og en klienthemmelighed fra denne proces.

3.1. Maven-konfiguration

Vi starter med at tilføje disse afhængigheder til vores projektpom-fil:

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

Startgenstanden samler alle Spring Security Client-relaterede afhængigheder, herunder:

  • det spring-security-oauth2-client afhængighed af OAuth 2.0 login og klientfunktionalitet
  • JOSE-biblioteket til JWT-support

Som sædvanligt kan vi finde den nyeste version af denne artefakt ved hjælp af Maven Central-søgemaskinen.

4. Grundlæggende konfiguration ved hjælp af Spring Boot

For det første starter vi med at konfigurere vores applikation til at bruge den klientregistrering, vi lige har oprettet med Google.

Brug af Spring Boot gør dette meget let, da alt hvad vi skal gøre er at definere to applikationsegenskaber:

forår: sikkerhed: oauth2: klient: registrering: google: klient-id: klienthemmelighed: 

Lad os starte vores applikation og prøve at få adgang til et slutpunkt nu. Vi ser, at vi bliver omdirigeret til en Google Login-side til vores OAuth 2.0-klient.

Det ser virkelig simpelt ud, men der foregår en hel del ting under emhætten her. Dernæst undersøger vi, hvordan Spring Security trækker dette af.

Tidligere i vores WebClient- og OAuth 2-supportindlæg analyserede vi internerne om, hvordan Spring Security håndterer OAuth 2.0-autorisationsservere og -klienter.

Derinde så vi, at vi skal levere yderligere data bortset fra klient-id og klienthemmelighed for at konfigurere en ClientRegistration eksempel med succes. Så hvordan fungerer dette?

Svaret er, Google er en velkendt udbyder, og derfor tilbyder rammen nogle foruddefinerede egenskaber for at gøre tingene lettere.

Vi kan se på disse konfigurationer i CommonOAuth2Provider enum.

For Google definerer den opregnede type egenskaber som:

  • standardomfanget, der skal bruges
  • autorisationens slutpunkt
  • Token-slutpunktet
  • UserInfo-slutpunktet, som også er en del af OIDC Core-specifikationen

4.1. Adgang til brugeroplysninger

Spring Security tilbyder en nyttig repræsentation af en bruger, som er registreret hos en OIDC-udbyder, the OidcUser enhed.

Bortset fra det grundlæggende OAuth2AuthenticatedPrincipal metoder, denne enhed tilbyder nogle nyttige funktioner:

  • hente ID-token-værdien og de krav, den indeholder
  • få de krav, der leveres af UserInfo-slutpunktet
  • generere et aggregat af de to sæt

Vi har let adgang til denne enhed i en controller:

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

Eller ved hjælp af SecurityContextHolder i en bønne:

Godkendelsesgodkendelse = SecurityContextHolder.getContext (). GetAuthentication (); hvis (authentication.getPrincipal () forekomst af OidcUser) {OidcUser principal = ((OidcUser) authentication.getPrincipal ()); // ...}

Hvis vi inspicerer rektor, vil vi se en masse nyttige oplysninger her, som brugerens navn, e-mail, profilbillede og lokalitet.

Desuden er det vigtigt at bemærke, at Spring tilføjer myndigheder til hovedstolen baseret på de omfang, den har modtaget fra udbyderen, foran "SCOPE_“. F.eks åben rækkevidde bliver en SCOPE_openid tildelt myndighed.

Disse myndigheder kan f.eks. Bruges til at begrænse adgangen til bestemte ressourcer:

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

5. OIDC i aktion

Indtil videre har vi lært, hvordan vi nemt kan implementere en OIDC Login-løsning ved hjælp af Spring Security

Vi har set fordelen ved at delegere brugeridentifikationsprocessen til en OpenID-udbyder, som igen leverer detaljerede nyttige oplysninger, selv på en skalerbar måde.

Men sandheden er, at vi hidtil ikke havde at gøre med noget OIDC-specifikt aspekt. Det betyder, at Spring gør det meste af arbejdet for os.

Derfor vil vi se, hvad der foregår bag kulisserne for at forstå bedre, hvordan denne specifikation omsættes og være i stand til at få mest muligt ud af det.

5.1. Loginprocessen

For at se dette tydeligt, lad os aktivere RestTemplate logger for at se de anmodninger, som tjenesten udfører:

logning: niveau: org.springframework.web.client.RestTemplate: DEBUG

Hvis vi kalder et sikret slutpunkt nu, ser vi, at tjenesten udfører det regelmæssige OAuth 2.0 autorisationskodeflow. Det skyldes, som vi sagde, denne specifikation er bygget oven på OAuth 2.0. Der er alligevel nogle forskelle.

For det første kan vi, afhængigt af den udbyder, vi bruger, og de omfang, vi har konfigureret, muligvis se, at tjenesten ringer til det UserInfo-slutpunkt, vi nævnte i starten.

Navnlig, hvis autorisationssvaret henter mindst en af profil, e-mail, adresse eller telefon anvendelsesområde, rammen vil kalde UserInfo-slutpunktet for at få yderligere oplysninger.

Selvom alt tyder på, at Google skal hente profil og e-mail omfang - da vi bruger dem i autorisationsanmodningen - henter OP i stedet deres brugerdefinerede kolleger, //www.googleapis.com/auth/userinfo.email og //www.googleapis.com/auth/userinfo.profileDerfor kalder Spring ikke slutpunktet.

Dette betyder, at alle de oplysninger, vi får, er en del af ID-token.

Vi kan tilpasse os denne adfærd ved at skabe og levere vores egen OidcUserService eksempel:

@Configuration offentlig klasse OAuth2LoginSecurityConfig udvider WebSecurityConfigurerAdapter {@Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster Undtagelse {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 (). godkendt ()) .oauth2Login (oauthLogin -> oauthLogin .userInfoEndpoint () .oidcUserService (googleUserService)); }}

Den anden forskel, vi vil observere, er et opkald til JWK Set URI. Som vi forklarede i vores JWS- og JWK-indlæg, bruges dette til at verificere den JWT-formaterede ID-tokensignatur.

Dernæst analyserer vi ID-token i detaljer.

5.2. ID-token

Naturligvis dækker OIDC-specifikationen og tilpasser sig mange forskellige scenarier. I dette tilfælde bruger vi strømning af autorisationskode, og protokollen angiver, at både adgangstoken og ID-token vil blive hentet som en del af Token-slutpunktssvaret.

Som vi sagde før, OidcUser enhed indeholder kravene indeholdt i ID-tokenet og det faktiske JWT-formaterede token, som kan inspiceres ved hjælp af jwt.io.

Oven i dette tilbyder Spring mange praktiske getters til at opnå de standardkrav, der er defineret i specifikationen på en ren måde.

Vi kan se, at ID-token indeholder nogle obligatoriske krav:

  • udstederidentifikatoren formateret som en URL (f.eks. “//accounts.google.com“)
  • et emne-id, som er en reference til slutbrugeren indeholdt af udstederen
  • udløbstiden for symbolet
  • tidspunktet, hvor symbolet blev udstedt
  • publikum, som indeholder OAuth 2.0-klient-id'et, som vi har konfigureret

Og også mange OIDC-standardkrav som dem, vi nævnte før (navn, landestandard, billede, e-mail).

Da disse er standard, kan vi forvente, at mange udbydere henter i det mindste nogle af disse felter og derfor letter udviklingen af ​​enklere løsninger.

5.3. Krav og rækkevidde

Som vi kan forestille os, svarer de krav, der hentes af OP, med de anvendelsesområder, vi (eller Spring Security) konfigurerede.

OIDC definerer nogle anvendelsesområder, der kan bruges til at anmode om de krav, der er defineret af OIDC:

  • profil, som kan bruges til at anmode om standardprofilkrav (f.eks. navn, foretrukket_brugernavn,billede, osv)
  • e-mail, for at få adgang til e-mail og email_verificeret Krav
  • adresse
  • telefon, til anmodninger om telefonnummer og telefonnummer_bekræftet Krav

Selvom Spring ikke understøtter det endnu, tillader specifikationen at anmode om enkeltkrav ved at specificere dem i autorisationsanmodningen.

6. Forårssupport til OIDC Discovery

Som vi forklarede i indledningen, indeholder OIDC mange forskellige funktioner bortset fra dets hovedformål.

De muligheder, vi skal analysere i dette afsnit og det følgende, er valgfri i OIDC. Derfor er det vigtigt at forstå, at der muligvis er OP'er, der ikke understøtter dem.

Specifikationen definerer en Discovery-mekanisme for en RP til at opdage OP og få de nødvendige oplysninger til at interagere med det.

I en nøddeskal giver OP'er et JSON-dokument med standardmetadata. Oplysningerne skal betjenes af et velkendt slutpunkt for udstederens placering, /.well-known/openid-configuration.

Spring drager fordel af dette ved at tillade os at konfigurere en ClientRegistration med kun en simpel ejendom, udstederens placering.

Men lad os hoppe lige ind i et eksempel for at se dette tydeligt.

Vi definerer en brugerdefineret ClientRegistration eksempel:

forår: sikkerhed: oauth2: klient: registrering: brugerdefineret-google: klient-id: klient-hemmelighed: udbyder: tilpasset-google: udsteder-uri: //accounts.google.com

Nu kan vi genstarte vores ansøgning og kontrollere logfiler for at bekræfte, at applikationen ringer til openid-konfiguration slutpunkt i opstartsprocessen.

Vi kan endda gennemse dette slutpunkt for at se på oplysningerne fra Google:

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

Vi kan for eksempel se autorisations-, token- og UserInfo-slutpunkter, som tjenesten skal bruge, og de understøttede omfang.

En særlig relevant note her er det faktum, at hvis Discovery-slutpunktet ikke er tilgængeligt på det tidspunkt, hvor tjenesten startes, så vil vores app ikke være i stand til at fuldføre startprocessen med succes.

7. OpenID Connect Session Management

Denne specifikation supplerer kernefunktionaliteten ved at definere:

  • forskellige måder at overvåge slutbrugerens loginstatus på OP løbende, så RP kan logge ud af en slutbruger, der er logget ud af OpenID-udbyderen
  • muligheden for at registrere RP-logout-URI'er med OP som en del af klientregistreringen, således at der underrettes, når slutbrugeren logger ud af OP
  • en mekanisme til at underrette OP om, at slutbrugeren har logget ud af webstedet og muligvis også vil logge ud af OP

Naturligvis understøtter ikke alle OP'er alle disse poster, og nogle af disse løsninger kan kun implementeres i en frontend-implementering via User-Agent.

I denne vejledning fokuserer vi på de muligheder, som Spring tilbyder til det sidste punkt på listen, RP-initieret Logout.

På dette tidspunkt, hvis vi logger ind på vores applikation, kan vi normalt få adgang til hvert slutpunkt.

Hvis vi logger ud (ringer til /Log ud slutpunkt), og vi fremsætter en anmodning til en sikret ressource bagefter, vi ser, at vi kan få svaret uden at skulle logge ind igen.

Dette er dog faktisk ikke sandt; hvis vi inspicerer fanen Netværk i browserdebug-konsollen, ser vi, at når vi rammer det sikrede slutpunkt anden gang, bliver vi omdirigeret til OP-godkendelsesendepunktet, og da vi stadig er logget ind der, gennemføres strømmen gennemsigtigt , der ender i det sikrede slutpunkt næsten øjeblikkeligt.

Selvfølgelig er dette måske ikke den ønskede adfærd i nogle tilfælde. Lad os se, hvordan vi kan implementere denne OIDC-mekanisme til at håndtere dette.

7.1. OpenID-udbyderens konfiguration

I dette tilfælde konfigurerer og bruger vi en Okta-forekomst som vores OpenID-udbyder. Vi vil ikke gå i detaljer om, hvordan du opretter forekomsten, men vi kan følge trinene i denne vejledning og huske på, at Spring Securitys standard-tilbagekaldsslutpunkt vil være / login / oauth2 / kode / okta.

I vores applikation kan vi definere klientregistreringsdata med egenskaber:

forår: sikkerhed: oauth2: klient: registrering: okta: klient-id: klienthemmelighed: udbyder: okta: udsteder-uri: //dev-123.okta.com

OIDC angiver, at OP-logout-slutpunktet kan specificeres i Discovery-dokumentet som slut_session_endpunkt element.

7.2. Det LogoutSuccessHandler Konfiguration

Derefter bliver vi nødt til at konfigurere HttpSikkerhed logout logik ved at give en tilpasset LogoutSuccessHandler eksempel:

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

Lad os nu se, hvordan vi kan oprette en LogoutSuccessHandler til dette formål ved hjælp af en særlig klasse leveret af Spring Security, OidcClientInitiatedLogoutSuccessHandler:

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

Derfor bliver vi nødt til at konfigurere denne URI som en gyldig logout Redirect URI i OP-klientens konfigurationspanel.

Det er klart, at OP-logout-konfigurationen er indeholdt i klientregistreringsopsætningen, da alt, hvad vi bruger til at konfigurere handleren, er ClientRegistrationRepository bønne til stede i sammenhængen.

Så hvad sker der nu?

Når vi har logget ind på vores ansøgning, kan vi sende en anmodning til /Log ud slutpunkt leveret af Spring Security.

Hvis vi tjekker netværkslogfilerne i browserens fejlfindingskonsol, vi ser, at vi blev omdirigeret til et OP-logout-slutpunkt, inden vi endelig fik adgang til den omdirigerings-URI, vi konfigurerede.

Næste gang vi får adgang til et slutpunkt i vores applikation, der kræver godkendelse, skal vi obligatorisk logge ind igen i vores OP-platform for at få tilladelser.

8. Konklusion

For at opsummere lærte vi i denne vejledning meget om de løsninger, der tilbydes af OpenID Connect, og hvordan vi kan implementere nogle af dem ved hjælp af Spring Security.

Som altid kan alle de komplette eksempler findes i vores GitHub repo.


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