Spring Security 5 - OAuth2 Login

1. Overzicht

Spring Security 5 introduceert een nieuwe OAuth2LoginConfigurer class die we kunnen gebruiken voor het configureren van een externe autorisatieserver.

In dit artikel, we zullen enkele van de verschillende configuratie-opties onderzoeken die beschikbaar zijn voor het oauth2Login () element.

2. Maven afhankelijkheden

In een Spring Boot-project hoeven we alleen de starter toe te voegen spring-boot-starter-oauth2-client:

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

In een niet-Boot-project moeten we naast de standaard Spring- en Spring Security-afhankelijkheden ook expliciet de spring-security-oauth2-client en spring-security-oauth2-jose afhankelijkheden:

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

3. Cliënten instellen

In een Spring Boot-project hoeven we alleen maar een paar standaard eigenschappen toe te voegen voor elke client die we willen configureren.

Laten we ons project opzetten om in te loggen met klanten die bij Google en Facebook zijn geregistreerd als authenticatieleveranciers.

3.1. Inloggegevens van de klant verkrijgen

Om de inloggegevens van de klant voor Google OAuth2-authenticatie te verkrijgen, gaat u naar de Google API Console - sectie "Inloggegevens".

Hier maken we aanmeldingsgegevens van het type "OAuth2 Client ID" voor onze webapplicatie. Dit resulteert erin dat Google een client-ID en geheim voor ons instelt.

We moeten ook een geautoriseerde omleidings-URI configureren in de Google Console, het pad waarnaar gebruikers worden omgeleid nadat ze zich hebben aangemeld bij Google.

Spring Boot configureert deze omleidings-URI standaard als / login / oauth2 / code / {registrationId}. Daarom voegen we voor Google de URI toe:

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

Om de clientreferenties voor authenticatie bij Facebook te verkrijgen, moeten we een toepassing registreren op de website van Facebook voor ontwikkelaars en de bijbehorende URI instellen als een "geldige OAuth-omleidings-URI":

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

3.3. Beveiligingsconfiguratie

Vervolgens moeten we de clientreferenties toevoegen aan het application.properties het dossier. De Spring Security-eigenschappen worden voorafgegaan door "Spring.security.oauth2.client.registration" gevolgd door de clientnaam en vervolgens de naam van de clienteigenschap:

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 =

Door deze eigenschappen voor ten minste één client toe te voegen, wordt het Oauth2ClientAutoConfiguration klasse die alle benodigde bonen opzet.

De automatische configuratie van webbeveiliging is gelijk aan het definiëren van een eenvoudig oauth2Login () element:

@Configuration public class SecurityConfig breidt WebSecurityConfigurerAdapter uit {@Override protected void configure (HttpSecurity http) genereert uitzondering {http.authorizeRequests () .anyRequest (). Geverifieerd (). En () .oauth2Login (); }}

Hier kunnen we de oauth2Login () element wordt op soortgelijke wijze gebruikt als reeds bekend httpBasic () en formLogin () elementen.

Wanneer we nu proberen toegang te krijgen tot een beschermde URL, geeft de applicatie een automatisch gegenereerde inlogpagina weer met twee clients:

3.4. Andere klanten

Merk op dat het Spring Security-project naast Google en Facebook ook standaardconfiguraties voor GitHub en Okta bevat. Deze standaardconfiguraties bieden alle benodigde informatie voor authenticatie, waardoor we alleen de inloggegevens van de klant kunnen invoeren.

Als we een andere authenticatieleverancier willen gebruiken die niet is geconfigureerd in Spring Security, moeten we de volledige configuratie definiëren, met informatie zoals autorisatie-URI en token-URI. Hier is een blik op de standaardconfiguraties in Spring Security om een ​​idee te krijgen van de benodigde eigenschappen.

4. Installatie in een niet-opstartproject

4.1. Een ClientRegistrationRepository Boon

Als we niet met een Spring Boot-applicatie werken, moeten we een ClientRegistrationRepository Boon die een interne weergave bevat van de cliëntinformatie die eigendom is van de autorisatieserver:

@Configuration @EnableWebSecurity @PropertySource ("classpath: application.properties") public class SecurityConfig breidt WebSecurityConfigurerAdapter uit {privé statische List clients = Arrays.asList ("google", "facebook"); @Bean openbare ClientRegistrationRepository clientRegistrationRepository () {Lijstregistraties = clients.stream () .map (c -> getRegistration (c)) .filter (registratie -> registratie! = Null) .collect (Collectors.toList ()); retourneer nieuwe InMemoryClientRegistrationRepository (registraties); }}

Hier maken we een InMemoryClientRegistrationRepository met een lijst van Klantregistratie voorwerpen.

4.2. Gebouw Klantregistratie Voorwerpen

Laten we eens kijken naar de getRegistration () methode die deze objecten bouwt:

private static String CLIENT_PROPERTY_KEY = "spring.security.oauth2.client.registration."; @Autowired privéomgeving env; private ClientRegistration getRegistration (String-client) {String clientId = env.getProperty (CLIENT_PROPERTY_KEY + client + ".client-id"); if (clientId == null) {retourneer null; } String clientSecret = env.getProperty (CLIENT_PROPERTY_KEY + client + ".client-secret"); if (client.equals ("google")) {return CommonOAuth2Provider.GOOGLE.getBuilder (client) .clientId (clientId) .clientSecret (clientSecret) .build (); } if (client.equals ("facebook")) {return CommonOAuth2Provider.FACEBOOK.getBuilder (client) .clientId (clientId) .clientSecret (clientSecret) .build (); } retourneer null; }

Hier lezen we de clientreferenties van een vergelijkbaar application.properties bestand en gebruik vervolgens het CommonOauth2Provider enum al gedefinieerd in Spring Security voor de rest van de client-eigenschappen voor Google- en Facebook-clients.

Elk Klantregistratie instantie komt overeen met een klant.

4.3. Het registreren van het ClientRegistrationRepository

Ten slotte moeten we een OAuth2AuthorizedClientService boon op basis van de ClientRegistrationRepository bean en registreer beide bij de oauth2Login () element:

@Override protected void configure (HttpSecurity http) genereert uitzondering {http.authorizeRequests (). AnyRequest (). Geauthenticeerd () .and () .oauth2Login () .clientRegistrationRepository (clientRegistrationRepository ()) .authorizedClientService (geautoriseerd); } @Bean openbare OAuth2AuthorizedClientService AuthorizedClientService () {retourneer nieuwe InMemoryOAuth2AuthorizedClientService (clientRegistrationRepository ()); }

Zoals hier blijkt, we kunnen de clientRegistrationRepository () methode van oauth2Login () om een ​​aangepaste registratierepository te registreren.

We zullen ook een aangepaste inlogpagina moeten definiëren, omdat deze niet automatisch meer wordt gegenereerd. We zullen hier meer informatie over zien in de volgende sectie.

Laten we doorgaan met het verder aanpassen van ons inlogproces.

5. Aanpassen oauth2Login ()

Er zijn verschillende elementen die het OAuth 2-proces gebruikt en die we kunnen aanpassen met oauth2Login () methoden.

Merk op dat al deze elementen standaardconfiguraties hebben in Spring Boot en dat expliciete configuratie niet vereist is.

Laten we eens kijken hoe we deze kunnen aanpassen in onze configuratie.

5.1. Aangepaste aanmeldingspagina

Hoewel Spring Boot een standaard inlogpagina voor ons genereert, willen we meestal onze eigen aangepaste pagina definiëren.

Laten we beginnen met het configureren van een nieuwe inlog-URL voor het oauth2Login () element met behulp van delogin pagina() methode:

@Override protected void configure (HttpSecurity http) genereert uitzondering {http.authorizeRequests () .antMatchers ("/ oauth_login") .permitAll () .anyRequest () .authenticated () .and () .oauth2Login () .loginPage ("/ oauth_login "); }

Hier hebben we onze inlog-URL ingesteld op / oauth_login.

Laten we vervolgens een LoginController met een methode die naar deze URL verwijst:

@Controller openbare klasse LoginController {privé statische tekenreeks autorisatieRequestBaseUri = "oauth2 / autorisatie"; Kaart oauth2AuthenticationUrls = nieuwe HashMap (); @Autowired private ClientRegistrationRepository clientRegistrationRepository; @GetMapping ("/ oauth_login") public String getLoginPage (Modelmodel) {// ... return "oauth_login"; }}

Deze methode moet een kaart van de beschikbare clients en hun autorisatie-eindpunten naar de weergave sturen, die we verkrijgen van de ClientRegistrationRepository Boon:

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

Ten slotte moeten we onze oauth_login.html bladzijde:

Login met:

Cliënt

Dit is een eenvoudige HTML-pagina met links voor authenticatie bij elke client.

Nadat we er wat styling aan hebben toegevoegd, kunnen we het uiterlijk van de inlogpagina wijzigen:

5.2. Aangepast authenticatiesucces en foutgedrag

We kunnen het post-authenticatiegedrag controleren door verschillende methoden te gebruiken:

  • defaultSuccessUrl () en failureUrl () - om de gebruiker om te leiden naar een bepaalde URL
  • successHandler () en failureHandler () - om aangepaste logica uit te voeren na het authenticatieproces

Laten we eens kijken hoe we aangepaste URL's kunnen instellen om de gebruiker om te leiden naar:

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

Als de gebruiker een beveiligde pagina heeft bezocht voordat hij zich heeft geverifieerd, wordt hij na het inloggen naar die pagina doorgestuurd; anders worden ze omgeleid naar /succesvol ingelogd.

Als we willen dat de gebruiker altijd naar het /succesvol ingelogd URL ongeacht of ze zich eerder op een beveiligde pagina bevonden of niet, we kunnen de methode gebruiken defaultSuccessUrl ("/ loginSuccess", true).

Om een ​​aangepaste handler te gebruiken, zouden we een klasse moeten maken die de AuthenticationSuccessHandler of AuthenticationFailureHandler interfaces, overschrijf de overgeërfde methoden en stel vervolgens de bonen in met behulp van de successHandler () en failureHandler () methoden.

5.3. Aangepast autorisatie-eindpunt

Het autorisatie-eindpunt is het eindpunt dat Spring Security gebruikt om een ​​autorisatieverzoek naar de externe server te sturen.

Eerste, laten we nieuwe eigenschappen instellen voor het autorisatie-eindpunt:

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

Hier hebben we het baseUri naar / oauth2 / authorize-client in plaats van de standaard / oauth2 / autorisatie. We stellen ook expliciet een autorisatieRequestRepository () boon die we moeten definiëren:

@Bean openbare AuthorizationRequestRepository authorizationRequestRepository () {retourneer nieuwe HttpSessionOAuth2AuthorizationRequestRepository (); }

In ons voorbeeld hebben we de door Spring geleverde implementatie voor onze boon gebruikt, maar we kunnen ook een aangepaste versie leveren.

5.4. Aangepast token-eindpunt

Het teken eindpunt verwerkt toegangstokens.

Laten we expliciet het tokenEndpoint ()met de standaard implementatie van de responsclient:

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

En hier is de antwoordclientboon:

@Bean openbaar OAuth2AccessTokenResponseClient accessTokenResponseClient () {retourneer nieuwe NimbusAuthorizationCodeTokenResponseClient (); }

Deze configuratie is dezelfde als de standaard configuratie en maakt gebruik van de Spring-implementatie die is gebaseerd op het uitwisselen van een autorisatiecode met de provider.

We kunnen natuurlijk ook een aangepaste responsclient vervangen.

5.5. Aangepast omleidings-eindpunt

Dit is het eindpunt waarnaar moet worden omgeleid na authenticatie bij de externe provider.

Laten we eens kijken hoe we het baseUri voor het omleidings-eindpunt:

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

De standaard-URI is login / oauth2 / code.

Merk op dat als we het wijzigen, we ook het redirectUriTemplate eigendom van elk Klantregistratie en voeg de nieuwe URI toe als een geautoriseerde omleidings-URI voor elke client.

5.6. Aangepast eindpunt voor gebruikersinformatie

Het eindpunt voor gebruikersinformatie is de locatie die we kunnen gebruiken om gebruikersinformatie te verkrijgen.

We kunnen dit eindpunt aanpassen met de userInfoEndpoint () methode. Hiervoor kunnen we methoden gebruiken zoals gebruikerService () en customUserType () om de manier te wijzigen waarop gebruikersinformatie wordt opgehaald.

6. Toegang tot gebruikersinformatie

Een veel voorkomende taak die we mogelijk willen bereiken, is het vinden van informatie over de ingelogde gebruiker. Voor deze, we kunnen een verzoek indienen bij het eindpunt van gebruikersinformatie.

Eerst moeten we de client ophalen die overeenkomt met het huidige gebruikerstoken:

@Autowired privé OAuth2AuthorizedClientService AuthorizedClientService; @GetMapping ("/ loginSuccess") openbare String getLoginInfo (Modelmodel, OAuth2AuthenticationToken-authenticatie) {OAuth2AuthorizedClient-client = geautoriseerdeClientService .loadAuthorizedClient (authentication.getAuthorizedClientRegistrationId (), authentication.getName ()); // ... retourneer "loginSuccess"; }

Vervolgens sturen we een verzoek naar het gebruikersinfo-eindpunt van de klant en halen we het userAttributes-kaart:

String userInfoEndpointUri = client.getClientRegistration () .getProviderDetails (). GetUserInfoEndpoint (). GetUri (); if (! StringUtils.isEmpty (userInfoEndpointUri)) {RestTemplate restTemplate = nieuwe RestTemplate (); HttpHeaders headers = nieuwe HttpHeaders (); headers.add (HttpHeaders.AUTHORIZATION, "Bearer" + client.getAccessToken () .getTokenValue ()); HttpEntity entity = nieuwe HttpEntity ("", headers); ResponseEntity response = restTemplate .exchange (userInfoEndpointUri, HttpMethod.GET, entity, Map.class); Kaart userAttributes = response.getBody (); model.addAttribute ("naam", userAttributes.get ("naam")); }

Door de naam eigendom als een Model attribuut, kunnen we het weergeven in de succesvol ingelogd bekijk als een welkomstbericht voor de gebruiker:

naast de naam, de userAttributes-kaart bevat ook eigenschappen zoals e-mail, familienaam,picture, landinstelling.

7. Conclusie

In dit artikel hebben we gezien hoe we de oauth2Login () element in Spring Security om te authenticeren bij verschillende providers zoals Google en Facebook. We hebben ook enkele veelvoorkomende scenario's doorlopen om dit proces aan te passen.

De volledige broncode van de voorbeelden is te vinden op GitHub.