Spring Security i OpenID Connect

Imajte na umu da je ovaj članak ažuriran na novi stog Spring Security OAuth 2.0. Vodič koji koristi naslijeđeni stog i dalje je dostupan.

1. Pregled

U ovom brzom vodiču usredotočit ćemo se na postavljanje OpenID Connect (OIDC) s Spring Security.

Predstavit ćemo različite aspekte ove specifikacije, a zatim ćemo vidjeti podršku koju Spring Security nudi za njezinu primjenu na OAuth 2.0 klijentu.

2. Uvod u brzi OpenID Connect

OpenID Connect je sloj identiteta izgrađen na vrhu protokola OAuth 2.0.

Stoga je jako važno znati OAuth 2.0 prije nego što zaronite u OIDC, posebno protok autorizacijskog koda.

OIDC paket specifikacija je opsežan; uključuje osnovne značajke i nekoliko drugih opcijskih mogućnosti, predstavljenih u različitim skupinama. Glavni su:

  • Jezgra: provjera autentičnosti i uporaba zahtjeva za komunikaciju podataka krajnjeg korisnika
  • Otkriće: određuje kako klijent može dinamički odrediti informacije o dobavljačima OpenID-a
  • Dinamička registracija: nalaže kako se klijent može registrirati kod davatelja usluge
  • Upravljanje sesijama: definira način upravljanja OIDC sesijama

Povrh svega, dokumenti razlikuju OAuth 2.0 poslužitelje za provjeru autentičnosti koji nude podršku za ovu specifikaciju, nazivajući ih "pružateljima OpenID-a" (OP) i OAuth 2.0 klijentima koji koriste OIDC kao pouzdane strane (RP). U ovom ćemo se članku pridržavati ove terminologije.

Vrijedno je znati i da klijent može zatražiti upotrebu ovog proširenja dodavanjem openid opseg u svom zahtjevu za odobrenje.

Konačno, još jedan aspekt koji je korisno razumjeti za ovaj vodič je činjenica da OP emitiraju informacije o krajnjem korisniku kao JWT koji se naziva „ID žeton”.

Sada da, spremni smo zaroniti dublje u OIDC svijet.

3. Postavljanje projekta

Prije nego što se usredotočimo na stvarni razvoj, morat ćemo registrirati OAuth 2.o klijenta kod našeg OpenID dobavljača.

U ovom ćemo slučaju koristiti Google kao dobavljača OpenID-a. Možemo slijediti ove upute za registraciju naše klijentske aplikacije na njihovoj platformi. Primijetite da openid opseg je prisutan prema zadanim postavkama.

URI za preusmjeravanje koji smo postavili u ovom procesu krajnja je točka u našoj usluzi: // localhost: 8081 / login / oauth2 / code / google.

Iz ovog postupka trebali bismo dobiti ID klijenta i tajnu klijenta.

3.1. Maven konfiguracija

Za početak ćemo dodati ove ovisnosti u našu datoteku pom projekta:

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

Početni artefakt agregira sve ovisnosti povezane s Spring Security Clientom, uključujući:

  • the proljeće-sigurnost-oauth2-klijent ovisnost za OAuth 2.0 prijavu i funkcionalnost klijenta
  • JOSE knjižnica za JWT podršku

Kao i obično, najnoviju verziju ovog artefakta možemo pronaći pomoću pretraživača Maven Central.

4. Osnovna konfiguracija pomoću Spring Boota

Prvo ćemo započeti konfiguriranjem naše aplikacije da koristi registraciju klijenta koju smo upravo stvorili s Googleom.

Korištenje Spring Boota to čini vrlo jednostavnim, jer sve što moramo učiniti je definirati dva svojstva aplikacije:

spring: security: oauth2: client: registration: google: client-id: client-secret: 

Pokrenimo našu aplikaciju i pokušajmo odmah pristupiti krajnjoj točki. Vidjet ćemo da ćemo biti preusmjereni na Google stranicu za prijavu za naš OAuth 2.0 klijent.

Izgleda stvarno jednostavno, ali ovdje se pod pokrovom događa dosta toga. Dalje ćemo istražiti kako Spring Security to izvodi.

Prije toga, u našem postu za podršku za WebClient i OAuth 2, analizirali smo interne podatke o tome kako Spring Security postupa s OAuth 2.0 autorizacijskim poslužiteljima i klijentima.

Tamo smo vidjeli da moramo pružiti dodatne podatke, osim Client Id-a i Client Secreta, kako bismo konfigurirali ClientRegistration primjer uspješno. Pa, kako to funkcionira?

Odgovor je, Google je poznati pružatelj usluga, pa stoga okvir nudi neka unaprijed definirana svojstva kako bi stvari olakšao.

Možemo pogledati te konfiguracije u CommonOAuth2Provider nabrajanje.

Za Google, nabrojani tip definira svojstva poput:

  • zadani opsezi koji će se koristiti
  • krajnja točka autorizacije
  • krajnja točka tokena
  • krajnja točka UserInfo, koja je također dio OIDC specifikacije jezgre

4.1. Pristup korisničkim informacijama

Spring Security nudi koristan prikaz korisnika Ravnatelja registriranog kod OIDC dobavljača, OidcUser entitet.

Osim osnovne OAuth2AuthenticatedPrincipal metode, ovaj entitet nudi neke korisne funkcije:

  • dohvatiti vrijednost ID tokena i potraživanja koja sadrži
  • dobiti zahtjeve koje pruža krajnja točka UserInfo
  • generiraju agregat dva skupa

Ovom entitetu možemo lako pristupiti u upravljaču:

@GetMapping ("/ oidc-principal") javni OidcUser getOidcUserPrincipal (@AuthenticationPrincipal OidcUser principal) {return glavni; }

Ili pomoću SecurityContextHolder u grahu:

Provjera autentičnosti = SecurityContextHolder.getContext (). GetAuthentication (); if (authentication.getPrincipal () instanceof OidcUser) {OidcUser principal = ((OidcUser) authentication.getPrincipal ()); // ...}

Ako pregledamo ravnatelja, ovdje ćemo vidjeti puno korisnih informacija, poput imena korisnika, adrese e-pošte, slike profila i lokalizacije.

Nadalje, važno je napomenuti da Spring dodaje ovlasti glavnici na temelju opsega koje je dobio od davatelja, s prefiksom "OPSEG_“. Na primjer, openid doseg postaje a SCOPE_openid dodijeljeno ovlaštenje.

Ta se tijela, na primjer, mogu koristiti za ograničavanje pristupa određenim resursima:

@EnableWebSecurity javna klasa MappedAuthorities proširuje WebSecurityConfigurerAdapter {zaštićena voidna konfiguracija (HttpSecurity http) {http .authorizeRequests (authiteRequests -> authreRequests .mvcMatchers ("/ my-endpoint") .hasAuthority ("SCOP". ; }}

5. OIDC na djelu

Do sada smo naučili kako lako možemo implementirati rješenje za prijavu OIDC koristeći Spring Security

Vidjeli smo korist koju donosi delegiranjem postupka identifikacije korisnika dobavljaču OpenID-a, koji zauzvrat daje detaljne korisne informacije, čak i na skalabilan način.

Ali istina je da se dosad nismo morali nositi s bilo kojim aspektom specifičnim za OIDC. To znači da proljeće većinu posla obavlja umjesto nas.

Stoga ćemo vidjeti što se događa iza kulisa kako bismo bolje razumjeli kako se ova specifikacija provodi i mogli izvući maksimum iz nje.

5.1. Postupak prijave

Da bismo to jasno vidjeli, omogućimo RestTemplate zapisnike da biste vidjeli zahtjeve koje usluga izvršava:

zapisivanje: razina: org.springframework.web.client.RestTemplate: DEBUG

Ako sada nazovemo sigurnu krajnju točku, vidjet ćemo da usluga provodi redoviti tijek autorizacijskog koda OAuth 2.0. To je zato što je, kao što smo rekli, ova specifikacija izgrađena na vrhu OAuth 2.0. Svejedno postoje neke razlike.

Prvo, ovisno o davatelju kojeg koristimo i opsezima koje smo konfigurirali, mogli bismo vidjeti da usluga upućuje poziv na krajnju točku UserInfo koju smo spomenuli na početku.

Naime, ako odgovor na odobrenje dohvati barem jedan od profil, e-mail, adresa ili telefon opsega, okvir će pozvati krajnju točku UserInfo radi dobivanja dodatnih informacija.

Iako bi sve značilo da bi Google trebao dohvatiti profil i e-mail opseg - budući da ih koristimo u zahtjevu za autorizacijom - OP umjesto toga dohvaća njihove prilagođene kolege, //www.googleapis.com/auth/userinfo.email i //www.googleapis.com/auth/userinfo.profile, stoga Proljeće ne naziva krajnju točku.

To znači da su sve informacije koje dobivamo dio ID tokena.

Takvom se ponašanju možemo prilagoditi stvaranjem i pružanjem vlastitog OidcUserService primjer:

@Configuration javna klasa OAuth2LoginSecurityConfig proširuje WebSecurityConfigurerAdapter {@Override zaštićena void konfiguracija (HttpSecurity http) baca izuzetak {Set googleScopes = new HashSet (); googleScopes.add ("//www.googleapis.com/auth/userinfo.email"); googleScopes.add ("//www.googleapis.com/auth/userinfo.profile"); OidcUserService googleUserService = novi OidcUserService (); googleUserService.setAccessibleScopes (googleScopes); http .authorizeRequests (authiteRequests -> authiteRequests .anyRequest (). authenticated ()) .oauth2Login (oauthLogin -> oauthLogin .userInfoEndpoint () .oidcUserService (googleUserService)); }}

Druga razlika koju ćemo primijetiti je poziv na JWK Set URI. Kao što smo objasnili u našem JWS i JWK postu, ovo se koristi za provjeru potpisa identificiranog ID-a u formatu JWT.

Dalje ćemo detaljno analizirati ID žeton.

5.2. ID žeton

Naravno, OIDC specifikacije pokrivaju i prilagođavaju se puno različitih scenarija. U ovom slučaju koristimo tijek autorizacijskog koda, a protokol ukazuje da će se i pristupni token i ID žeton dohvatiti kao dio odgovora krajnje točke žetona.

Kao što smo već rekli, OidcUser entitet sadrži zahtjeve sadržane u ID žetonu i stvarni token u formatu JWT, koji se može pregledati pomoću jwt.io.

Povrh svega, Spring nudi mnoštvo praktičnih dohvatača kako bi se na čist način postigle standardne tvrdnje definirane specifikacijom.

Vidimo da ID žeton uključuje neke obvezne zahtjeve:

  • identifikator izdavatelja oblikovan kao URL (npr. "//accounts.google.com“)
  • ID predmeta, što je referenca na krajnjeg korisnika koju sadrži izdavač
  • vrijeme isteka tokena
  • vrijeme izdavanja žetona
  • publici, koja će sadržavati ID klijenta OAuth 2.0 koji smo konfigurirali

I također mnogi OIDC standardni zahtjevi poput onih koje smo prije spomenuli (Ime, lokalitet, slika, e-mail).

Budući da su to standardno, možemo očekivati ​​da će mnogi pružatelji usluga dohvatiti barem neka od ovih polja, što olakšava razvoj jednostavnijih rješenja.

5.3. Zahtjevi i opseg

Kao što možemo zamisliti, potraživanja koja OP dohvaća odgovaraju opsezima koje smo konfigurirali (ili Spring Security).

OIDC definira neke opsege koji se mogu koristiti za traženje zahtjeva definiranih od strane OIDC:

  • profil, koji se može koristiti za traženje zadanih zahtjeva za profil (npr. ime, željeno_korisničko ime,slika, itd)
  • e-mail, za pristup e-mail i potvrđena_e-pošta Zahtjevi
  • adresa
  • telefon, na zahtjeve broj telefona i telefonski_broj_provjeren Zahtjevi

Iako ga Spring još ne podržava, specifikacija omogućuje traženje pojedinačnih zahtjeva tako što ih navodi u Zahtjevu za autorizacijom.

6. Proljetna podrška za OIDC Discovery

Kao što smo objasnili u uvodu, OIDC uključuje mnoge različite značajke, osim svoje osnovne svrhe.

Sposobnosti koje ćemo analizirati u ovom i sljedećim odjeljcima nisu potrebne u OIDC-u. Stoga je važno razumjeti da možda postoje operativni programi koji ih ne podržavaju.

Specifikacija definira mehanizam otkrivanja za RP za otkrivanje OP-a i dobivanje podataka potrebnih za interakciju s njim.

Ukratko, OP pružaju JSON dokument standardnih metapodataka. Informacije mora dostaviti dobro poznata krajnja točka mjesta izdavatelja, /.dobro- poznato/openid-konfiguracija.

Spring koristi od toga dopuštajući nam da konfiguriramo a ClientRegistration sa samo jednim jednostavnim svojstvom, mjestom izdavatelja.

Ali, krenimo odmah u primjer da to jasno vidimo.

Definirat ćemo običaj ClientRegistration primjer:

spring: security: oauth2: client: registration: custom-google: client-id: client-secret: provider: custom-google: issuer-uri: //accounts.google.com

Sada možemo ponovno pokrenuti našu aplikaciju i provjeriti zapisnike kako bismo potvrdili da aplikacija poziva openid-konfiguracija krajnja točka u procesu pokretanja.

Čak možemo pregledavati ovu krajnju točku da bismo pogledali informacije koje pruža Google:

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

Možemo vidjeti, na primjer, autorizaciju, token i UserInfo krajnje točke koje usluga mora koristiti i podržane opsege.

Ovdje je posebno relevantna napomena da ako konačna točka Discovery nije dostupna u trenutku pokretanja usluge, naša aplikacija neće moći uspješno dovršiti postupak pokretanja.

7. Upravljanje sesijama OpenID Connect

Ova specifikacija nadopunjuje osnovnu funkcionalnost definiranjem:

  • različiti načini za kontinuirano praćenje statusa prijave krajnjeg korisnika na OP-u, tako da RP može odjaviti krajnjeg korisnika koji se odjavio iz dobavljača OpenID-a
  • mogućnost registracije URI-ja za odjavu RP-a s OP-om kao dio registracije klijenta, tako da bude obaviješten kad se krajnji korisnik odjavi iz OP-a
  • mehanizam za obavještavanje OP-a da se krajnji korisnik odjavio s web-mjesta te da bi se možda želio odjaviti i iz OP-a

Prirodno, ne podržavaju svi OP sve ove stavke, a neka od tih rješenja mogu se implementirati samo u front-end implementaciji putem User-Agenta.

U ovom uputstvu usredotočit ćemo se na mogućnosti koje nudi Spring za posljednju stavku popisa, odjavu koju pokreće RP.

U ovom trenutku, ako se prijavimo u našu aplikaciju, možemo normalno pristupiti svakoj krajnjoj točki.

Ako se odjavimo (pozivamo /Odjavite se krajnja točka), a nakon toga podnesemo zahtjev osiguranom resursu, vidjet ćemo da možemo dobiti odgovor bez potrebe za ponovnom prijavom.

Međutim, to zapravo nije istina; ako pregledamo karticu Mreža u konzoli za otklanjanje pogrešaka preglednika, vidjet ćemo da ćemo se po drugi put kada dođemo do osigurane krajnje točke preusmjeriti na krajnju točku autorizacije OP-a, a budući da smo tamo još uvijek prijavljeni, tok je dovršen transparentno , završavajući u osiguranoj krajnjoj točki gotovo trenutno.

Naravno, to u nekim slučajevima možda nije željeno ponašanje. Pogledajmo kako možemo implementirati ovaj OIDC mehanizam da bismo se nosili s tim.

7.1. Konfiguracija OpenID dobavljača

U ovom ćemo slučaju konfigurirati i koristiti instancu Okta kao našeg OpenID dobavljača. Nećemo ulaziti u detalje o tome kako stvoriti instancu, ali možemo slijediti korake u ovom vodiču, imajući na umu da će zadana krajnja točka povratnog poziva Spring Securityja biti / login / oauth2 / code / okta.

U našoj aplikaciji možemo definirati podatke o registraciji klijenta sa svojstvima:

proljeće: sigurnost: oauth2: klijent: registracija: okta: klijent-id: klijent-tajna: pružatelj: okta: izdavač-uri: //dev-123.okta.com

OIDC ukazuje da se krajnja točka odjave iz OP-a može navesti u dokumentu Discovery kao krajnja_sesija_krajna točka element.

7.2. The LogoutSuccessHandler Konfiguracija

Dalje, morat ćemo konfigurirati HttpSecurity logika odjave pružanjem prilagođenog LogoutSuccessHandler primjer:

@Override zaštićena void konfiguracija (HttpSecurity http) baca izuzetak {http .authorizeRequests (autorizacijaRequests -> authreRequests .mvcMatchers ("/ home"). DozvolaAll () .anyRequest (). ()) .logout (odjava -> odjava .logoutSuccessHandler (oidcLogoutSuccessHandler ())); }

Sada da vidimo kako možemo stvoriti LogoutSuccessHandler u tu svrhu koristeći posebnu klasu koju pruža Spring Security, OidcClientInitiatedLogoutSuccessHandler:

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

Slijedom toga, morat ćemo postaviti ovaj URI kao valjani URI za preusmjeravanje pri odjavi na konfiguracijskoj ploči OP klijenta.

Jasno je da je konfiguracija odjave iz OP-a sadržana u postavkama registracije klijenta, jer sve što koristimo za konfiguriranje voditelja je ClientRegistrationRepository grah prisutan u kontekstu.

Pa, što će se sada dogoditi?

Nakon što se prijavimo u našu aplikaciju, možemo poslati zahtjev na /Odjavite se krajnju točku koju pruža Spring Security.

Ako provjerimo mrežne zapisnike u konzoli za otklanjanje pogrešaka preglednika, vidjet ćemo da smo preusmjereni na krajnju točku odjave iz OP-a prije konačnog pristupa URI-ju za preusmjeravanje koji smo konfigurirali.

Sljedeći put kad pristupimo krajnjoj točki u našoj aplikaciji koja zahtijeva provjeru autentičnosti, morat ćemo se ponovno morati prijaviti na našu OP platformu da bismo dobili dozvole.

8. Zaključak

Da rezimiramo, u ovom smo tutorijalu naučili puno o rješenjima koja nudi OpenID Connect i kako neke od njih možemo implementirati pomoću Spring Security.

Kao i uvijek, sve cjelovite primjere možete pronaći u našem GitHub repo-u.


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