Spring Security 5 - OAuth2-login

1. Oversigt

Spring Security 5 introducerer en ny OAuth2LoginConfigurer klasse, som vi kan bruge til at konfigurere en ekstern autorisationsserver.

I denne artikel Vi undersøger nogle af de forskellige konfigurationsmuligheder, der er tilgængelige for oauth2Login () element.

2. Maven-afhængigheder

I et Spring Boot-projekt er alt, hvad vi har brug for, at tilføje starteren spring-boot-starter-oauth2-klient:

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

I et ikke-boot-projekt skal vi ud over standardafhængigheden af ​​Spring og Spring Security også udtrykkeligt tilføje spring-security-oauth2-client og fjeder-sikkerhed-oauth2-jose afhængigheder:

 org.springframework.security spring-security-oauth2-client 5.3.4.RELEASE org.springframework.security spring-security-oauth2-jose 5.3.4.RELEASE 

3. Opsætning af klienter

I et Spring Boot-projekt er alt, hvad vi skal gøre, at tilføje et par standardegenskaber til hver klient, vi vil konfigurere.

Lad os oprette vores projekt til login med klienter, der er registreret hos Google og Facebook som godkendelsesudbydere.

3.1. Opnåelse af klientoplysninger

For at få klientlegitimationsoplysninger til Google OAuth2-godkendelse skal du gå videre til Google API-konsol - sektion "Legitimationsoplysninger".

Her opretter vi legitimationsoplysninger af typen “OAuth2 Client ID” til vores webapplikation. Dette resulterer i, at Google opretter et klient-id og en hemmelighed for os.

Vi er også nødt til at konfigurere en autoriseret omdirigerings-URI i Google-konsollen, hvilket er den sti, som brugerne omdirigeres til, når de har logget ind med Google.

Spring Boot konfigurerer som standard denne omdirigerings-URI som / login / oauth2 / code / {registrationId}. Derfor tilføjer vi URI til Google:

// localhost: 8081 / login / oauth2 / code / google

For at opnå klientlegitimationsoplysninger til godkendelse med Facebook er vi nødt til at registrere en applikation på Facebook for Developers-webstedet og oprette den tilsvarende URI som en "Gyldig OAuth-omdirigerings-URI":

// localhost: 8081 / login / oauth2 / code / facebook

3.3. Sikkerhedskonfiguration

Dernæst skal vi tilføje klientlegitimationsoplysninger til application.properties fil. Spring Security-egenskaberne er forud for “Spring.security.oauth2.client.registration” efterfulgt af klientnavnet, derefter navnet på klientegenskaben:

spring.security.oauth2.client.registration.google.client-id = spring.security.oauth2.client.registration.google.client-secret = spring.security.oauth2.client.registration.facebook.client-id = spring. security.oauth2.client.registration.facebook.client-secret =

Tilføjelse af disse egenskaber for mindst en klient aktiverer Oauth2ClientAutoConfiguration klasse som sætter alle de nødvendige bønner op.

Den automatiske websikkerhedskonfiguration svarer til at definere en simpel oauth2Login () element:

@Configuration offentlig klasse SecurityConfig udvider WebSecurityConfigurerAdapter {@Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http.authorizeRequests () .anyRequest (). Godkendt () .and () .oauth2Login (); }}

Her kan vi se oauth2Login () element anvendes på en måde, der ligner allerede kendt httpBasic () og formLogin () elementer.

Når vi nu forsøger at få adgang til en beskyttet URL, viser applikationen en automatisk genereret login-side med to klienter:

3.4. Andre klienter

Bemærk, at Spring Security-projektet ud over Google og Facebook også indeholder standardkonfigurationer for GitHub og Okta. Disse standardkonfigurationer giver alle de nødvendige oplysninger til godkendelse, hvilket er det, der giver os mulighed for kun at indtaste klientoplysningerne.

Hvis vi vil bruge en anden godkendelsesudbyder, der ikke er konfigureret i Spring Security, skal vi definere den fulde konfiguration med oplysninger såsom autorisations-URI og token-URI. Her er et kig på standardkonfigurationerne i Spring Security for at få en idé om de nødvendige egenskaber.

4. Opsætning i et ikke-boot-projekt

4.1. Oprettelse af en ClientRegistrationRepository Bønne

Hvis vi ikke arbejder med en Spring Boot-applikation, skal vi definere en ClientRegistrationRepository bønne der indeholder en intern repræsentation af klientoplysninger, der ejes af autorisationsserveren:

@Configuration @EnableWebSecurity @PropertySource ("classpath: application.properties") offentlig klasse SecurityConfig udvider WebSecurityConfigurerAdapter {private static List clients = Arrays.asList ("google", "facebook"); @Bean offentlig ClientRegistrationRepository clientRegistrationRepository () {Listeregistreringer = clients.stream () .map (c -> getRegistration (c)) .filter (registrering -> registrering! = Null) .collect (Collectors.toList ()); returner nyt InMemoryClientRegistrationRepository (registreringer); }}

Her opretter vi en InMemoryClientRegistrationRepository med en liste over ClientRegistration genstande.

4.2. Bygning ClientRegistration Objekter

Lad os se getRegistration () metode, der bygger disse objekter:

privat statisk streng CLIENT_PROPERTY_KEY = "spring.security.oauth2.client.registration."; @Autowired private Environment env; privat ClientRegistration getRegistration (String client) {String clientId = env.getProperty (CLIENT_PROPERTY_KEY + client + ".client-id"); hvis (clientId == null) {return null; } String clientSecret = env.getProperty (CLIENT_PROPERTY_KEY + klient + ".client-secret"); hvis (client.equals ("google")) {returner CommonOAuth2Provider.GOOGLE.getBuilder (client) .clientId (clientId) .clientSecret (clientSecret) .build (); } hvis (client.equals ("facebook")) {returner CommonOAuth2Provider.FACEBOOK.getBuilder (client) .clientId (clientId) .clientSecret (clientSecret) .build (); } returnere null; }

Her læser vi klientoplysningerne fra et lignende application.properties fil og derefter bruge CommonOauth2Provider enum, der allerede er defineret i Spring Security for resten af ​​klientegenskaberne for Google- og Facebook-klienter.

Hver ClientRegistration instans svarer til en klient.

4.3. Registrering af ClientRegistrationRepository

Endelig er vi nødt til at oprette en OAuth2AuthorizedClientService bønne baseret på ClientRegistrationRepository bønne og registrer begge med oauth2Login () element:

@ Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http.authorizeRequests (). AnyRequest (). Godkendt (). Og () .oauth2Login () .clientRegistrationRepository (clientRegistrationRepository ()) .authorizedClientService (autoriseretClientService) } @Bean offentlig OAuth2AuthorizedClientService autoriseretClientService () {returner ny InMemoryOAuth2AuthorizedClientService (clientRegistrationRepository ()); }

Som det fremgår her, vi kan bruge clientRegistrationRepository () metode til oauth2Login () for at registrere et brugerdefineret registreringslager.

Vi bliver også nødt til at definere en brugerdefineret login-side, da den ikke længere genereres automatisk. Vi ser mere information om dette i det næste afsnit.

Lad os fortsætte med yderligere tilpasning af vores loginproces.

5. Tilpasning oauth2Login ()

Der er flere elementer, som OAuth 2-processen bruger, og som vi kan tilpasse ved hjælp af oauth2Login () metoder.

Bemærk, at alle disse elementer har standardkonfigurationer i Spring Boot, og eksplicit konfiguration er ikke påkrævet.

Lad os se, hvordan vi kan tilpasse disse i vores konfiguration.

5.1. Side med brugerdefineret login

Selvom Spring Boot genererer en standard login-side til os, vil vi normalt definere vores egen tilpassede side.

Lad os starte med at konfigurere en ny login-URL til oauth2Login () ved hjælp afloginPage () metode:

@ Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http.authorizeRequests () .antMatchers ("/ oauth_login") .permitAll () .anyRequest () .authenticated () .and () .oauth2Login () .loginPage ("/ oauth_login "); }

Her har vi konfigureret vores login-URL til at være / oauth_login.

Lad os derefter definere en LoginController med en metode, der kortlægges til denne URL:

@Controller offentlig klasse LoginController {privat statisk streng autorisationRequestBaseUri = "oauth2 / autorisation"; Kort oauth2AuthenticationUrls = ny HashMap (); @Autowired privat ClientRegistrationRepository clientRegistrationRepository; @GetMapping ("/ oauth_login") offentlig streng getLoginPage (modelmodel) {// ... returner "oauth_login"; }}

Denne metode skal sende et kort over de tilgængelige klienter og deres godkendelsesendepunkter til visningen, som vi får fra ClientRegistrationRepository bønne:

public String getLoginPage (Model model) {Iterable clientRegistrations = null; ResolvableType type = ResolvableType.forInstance (clientRegistrationRepository) .as (Iterable.class); hvis (type! = ResolvableType.NONE && ClientRegistration.class.isAssignableFrom (type.resolveGenerics () [0])) {clientRegistrations = (Iterable) clientRegistrationRepository; } clientRegistrations.forEach (registrering -> oauth2AuthenticationUrls.put (registrering.getClientName (), authorisationRequestBaseUri + "/" + registrering.getRegistrationId ())); model.addAttribute ("urls", oauth2AuthenticationUrls); returner "oauth_login"; }

Endelig er vi nødt til at definere vores oauth_login.html side:

Login med:

Klient

Dette er en simpel HTML-side, der viser links til godkendelse med hver klient.

Efter at have tilføjet noget styling til det kan vi ændre udseendet på login-siden:

5.2. Brugerdefineret godkendelse Succes og fejladfærd

Vi kan kontrollere adfærden efter godkendelse ved hjælp af forskellige metoder:

  • defaultSuccessUrl () og failureUrl () - at omdirigere brugeren til en given URL
  • successHandler () og failHandler () - at udføre brugerdefineret logik efter godkendelsesprocessen

Lad os se, hvordan vi kan indstille brugerdefinerede URL'er til at omdirigere brugeren til:

.oauth2Login () .defaultSuccessUrl ("/ loginSuccess") .failureUrl ("/ loginFailure");

Hvis brugeren besøgte en beskyttet side inden godkendelse, vil de blive omdirigeret til den side efter login. ellers omdirigeres de til / loginSucces.

Hvis vi ønsker, at brugeren altid skal sendes til / loginSucces URL uanset om de var på en beskyttet side før eller ej, kan vi bruge metoden defaultSuccessUrl (“/ loginSuccess”, true).

For at bruge en brugerdefineret handler skal vi oprette en klasse, der implementerer AuthenticationSuccessHandler eller AuthenticationFailureHandler grænseflader, tilsidesætter de arvede metoder, og indstil derefter bønnerne ved hjælp af successHandler () og failHandler () -metoder.

5.3. Slutpunkt for brugerdefineret autorisation

Autorisationsendepunktet er det slutpunkt, som Spring Security bruger til at udløse en autorisationsanmodning til den eksterne server.

Først, lad os indstille nye egenskaber til godkendelsesendepunktet:

.oauth2Login () .authorizationEndpoint () .baseUri ("/ oauth2 / authorize-client") .authorizationRequestRepository (authorisationRequestRepository ());

Her har vi ændret baseUri til / oauth2 / authorize-client i stedet for standard / oauth2 / autorisation. Vi indstiller også eksplicit en autorisationsforespørgsel () bønne, som vi skal definere:

@Bean public AuthorizationRequestRepository authorisationRequestRepository () {returner nyt HttpSessionOAuth2AuthorizationRequestRepository (); }

I vores eksempel har vi brugt den foråret leverede implementering til vores bønne, men vi kunne også levere en brugerdefineret.

5.4. Custom Token slutpunkt

Tokenet slutpunkt behandler adgangstokener.

Lad os udtrykkeligt konfigurere tokenEndpoint ()med standardrespons klientimplementering:

.oauth2Login () .tokenEndpoint () .accessTokenResponseClient (accessTokenResponseClient ());

Og her er svarklientbønnen:

@Bean public OAuth2AccessTokenResponseClient accessTokenResponseClient () {returner ny NimbusAuthorizationCodeTokenResponseClient (); }

Denne konfiguration er den samme som standard og bruger Spring-implementeringen, der er baseret på udveksling af en autorisationskode med udbyderen.

Naturligvis kunne vi også erstatte en brugerdefineret svarklient.

5.5. Tilpasset omdirigering slutpunkt

Dette er slutpunktet at omdirigere til efter godkendelse med den eksterne udbyder.

Lad os se, hvordan vi kan ændre baseUri til omdirigering slutpunkt:

.oauth2Login () .redirectionEndpoint () .baseUri ("/ oauth2 / redirect")

Standard URI er login / oauth2 / kode.

Bemærk, at hvis vi ændrer det, skal vi også opdatere redirectUriTemplate hver ejendom ClientRegistration og tilføj den nye URI som en autoriseret omdirigerings-URI for hver klient.

5.6. Slutpunkt for brugerdefineret brugerinformation

Brugerinfo-slutpunktet er det sted, vi kan udnytte for at få brugeroplysninger.

Vi kan tilpasse dette slutpunkt ved hjælp af userInfoEndpoint () metode. Til dette kan vi bruge metoder som userService () og customUserType () for at ændre den måde, brugerinformation hentes på.

6. Adgang til brugerinformation

En fælles opgave, vi måske ønsker at opnå, er at finde oplysninger om den indloggede bruger. For det, vi kan stille en anmodning til slutinformationen om brugerinformation.

Først bliver vi nødt til at få klienten svarende til det aktuelle brugertoken:

@Autowired privat OAuth2AuthorizedClientService autoriseretClientService; @GetMapping ("/ loginSuccess") offentlig streng getLoginInfo (modelmodel, OAuth2AuthenticationToken-godkendelse) {OAuth2AuthorizedClient-klient = autoriseretClientService .loadAuthorizedClient (authentication.getAuthorizedClientRegistrationId (), authentication.getName ()); // ... return "loginSuccess"; }

Derefter sender vi en anmodning til klientens brugerinfo-slutpunkt og henter userAttributes Kort:

String userInfoEndpointUri = client.getClientRegistration () .getProviderDetails (). GetUserInfoEndpoint (). GetUri (); hvis (! StringUtils.isEmpty (userInfoEndpointUri)) {RestTemplate restTemplate = ny RestTemplate (); HttpHeaders headers = nye HttpHeaders (); headers.add (HttpHeaders.AUTHORIZATION, "Bearer" + client.getAccessToken () .getTokenValue ()); HttpEntity-enhed = ny HttpEntity ("", overskrifter); ResponseEntity response = restTemplate .exchange (userInfoEndpointUri, HttpMethod.GET, entity, Map.class); Kort brugerAttributter = respons.getBody (); model.addAttribute ("navn", userAttributes.get ("navn")); }

Ved at tilføje navn ejendom som en Model -attribut, kan vi vise det i loginSuccess se som velkomstbesked til brugeren:

udover det navn, det brugerattributkort indeholder også egenskaber såsom e-mail, familie_navn,billede, landestandard.

7. Konklusion

I denne artikel har vi set, hvordan vi kan bruge oauth2Login () element i Spring Security for at godkende med forskellige udbydere som Google og Facebook. Vi har også gennemgået nogle almindelige scenarier for at tilpasse denne proces.

Den fulde kildekode for eksemplerne kan findes på GitHub.