Spring Security 5 - OAuth2-Anmeldung

1. Übersicht

Spring Security 5 führt eine neue OAuth2LoginConfigurer- Klasse ein, mit der wir einen externen Autorisierungsserver konfigurieren können.

In diesem Artikel werden einige der verschiedenen Konfigurationsoptionen erläutert, die für das Element oauth2Login () verfügbar sind .

2. Maven-Abhängigkeiten

In einem Spring Boot-Projekt müssen wir lediglich den Starter- Spring-Boot-Starter-oauth2-Client hinzufügen :

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

In einem Nicht-Boot-Projekt müssen zusätzlich zu den Standardabhängigkeiten Spring und Spring Security auch die Abhängigkeiten spring-security-oauth2-client und spring-security-oauth2-jose explizit hinzugefügt werden :

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

3. Clients einrichten

In einem Spring Boot-Projekt müssen wir lediglich einige Standardeigenschaften für jeden Client hinzufügen, den wir konfigurieren möchten.

Richten wir unser Projekt für die Anmeldung bei Kunden ein, die bei Google und Facebook als Authentifizierungsanbieter registriert sind.

3.1. Abrufen von Client-Anmeldeinformationen

Informationen zum Abrufen von Client-Anmeldeinformationen für die Google OAuth2-Authentifizierung finden Sie in der Google API-Konsole - Abschnitt "Anmeldeinformationen".

Hier erstellen wir Anmeldeinformationen vom Typ "OAuth2 Client ID" für unsere Webanwendung. Dies führt dazu, dass Google eine Kunden-ID und ein Geheimnis für uns einrichtet.

Außerdem müssen wir in der Google-Konsole einen autorisierten Umleitungs-URI konfigurieren. Dies ist der Pfad, auf den Nutzer umgeleitet werden, nachdem sie sich erfolgreich bei Google angemeldet haben.

Standardmäßig konfiguriert Spring Boot diesen Umleitungs-URI als / login / oauth2 / code / {Registrierungs-ID}. Daher fügen wir für Google den URI hinzu:

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

Um die Client-Anmeldeinformationen für die Authentifizierung bei Facebook zu erhalten, müssen wir eine Anwendung auf der Facebook for Developers-Website registrieren und den entsprechenden URI als "Valid OAuth Redirect URI" einrichten:

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

3.3. Sicherheitskonfiguration

Als Nächstes müssen wir die Client-Anmeldeinformationen zur Datei application.properties hinzufügen . Den Spring Security-Eigenschaften wird "spring.security.oauth2.client.registration" vorangestellt, gefolgt vom Client-Namen und dem Namen der Client-Eigenschaft:

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=

Durch Hinzufügen dieser Eigenschaften für mindestens einen Client wird die Oauth2ClientAutoConfiguration- Klasse aktiviert , die alle erforderlichen Beans einrichtet .

Die automatische Web-Sicherheitskonfiguration entspricht der Definition eines einfachen oauth2Login () -Elements:

@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login(); } }

Hier sehen wir, dass das Element oauth2Login () auf ähnliche Weise wie bereits bekannte Elemente httpBasic () und formLogin () verwendet wird.

Wenn wir nun versuchen, auf eine geschützte URL zuzugreifen, zeigt die Anwendung eine automatisch generierte Anmeldeseite mit zwei Clients an:

3.4. Andere Kunden

Beachten Sie, dass das Spring Security-Projekt neben Google und Facebook auch Standardkonfigurationen für GitHub und Okta enthält. Diese Standardkonfigurationen bieten alle erforderlichen Informationen für die Authentifizierung. Auf diese Weise können wir nur die Client-Anmeldeinformationen eingeben.

Wenn Sie einen anderen Authentifizierungsanbieter verwenden möchten, der nicht in Spring Security konfiguriert ist, müssen Sie die vollständige Konfiguration mit Informationen wie Autorisierungs-URI und Token-URI definieren. Hier sehen Sie die Standardkonfigurationen in Spring Security, um eine Vorstellung von den erforderlichen Eigenschaften zu erhalten.

4. Setup in einem Nicht-Boot-Projekt

4.1. Erstellen einer ClientRegistrationRepository Bean

Wenn wir nicht mit einer Spring Boot-Anwendung arbeiten, müssen wir eine ClientRegistrationRepository- Bean definieren , die eine interne Darstellung der Clientinformationen des Autorisierungsservers enthält:

@Configuration @EnableWebSecurity @PropertySource("classpath:application.properties") public class SecurityConfig extends WebSecurityConfigurerAdapter { private static List clients = Arrays.asList("google", "facebook"); @Bean public ClientRegistrationRepository clientRegistrationRepository() { List registrations = clients.stream() .map(c -> getRegistration(c)) .filter(registration -> registration != null) .collect(Collectors.toList()); return new InMemoryClientRegistrationRepository(registrations); } }

Hier erstellen wir ein InMemoryClientRegistrationRepository mit einer Liste von ClientRegistration- Objekten.

4.2. Gebäude ClientRegistration Objekte

Sehen wir uns die Methode getRegistration () an , mit der diese Objekte erstellt werden:

private static String CLIENT_PROPERTY_KEY = "spring.security.oauth2.client.registration."; @Autowired private Environment env; private ClientRegistration getRegistration(String client) { String clientId = env.getProperty( CLIENT_PROPERTY_KEY + client + ".client-id"); if (clientId == null) { return 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(); } return null; }

Hier lesen wir die Client-Anmeldeinformationen aus einer ähnlichen Datei application.properties und verwenden dann die in Spring Security bereits definierte CommonOauth2Provider-Enumeration für die restlichen Client-Eigenschaften für Google- und Facebook-Clients.

Jede ClientRegistration- Instanz entspricht einem Client.

4.3. Registrieren des ClientRegistrationRepository

Schließlich müssen wir eine OAuth2AuthorizedClientService- Bean basierend auf der ClientRegistrationRepository- Bean erstellen und beide mit dem oauth2Login () -Element registrieren :

@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated() .and() .oauth2Login() .clientRegistrationRepository(clientRegistrationRepository()) .authorizedClientService(authorizedClientService()); } @Bean public OAuth2AuthorizedClientService authorizedClientService() { return new InMemoryOAuth2AuthorizedClientService( clientRegistrationRepository()); }

As evidenced here, we can use the clientRegistrationRepository() method of oauth2Login() to register a custom registration repository.

We'll also have to define a custom login page, as it won't be automatically generated anymore. We'll see more information on this in the next section.

Let's continue with further customization of our login process.

5. Customizing oauth2Login()

There are several elements that the OAuth 2 process uses and that we can customize using oauth2Login() methods.

Note that all these elements have default configurations in Spring Boot and explicit configuration isn't required.

Let's see how we can customize these in our configuration.

5.1. Custom Login Page

Even though Spring Boot generates a default login page for us, we'll usually want to define our own customized page.

Let's start with configuring a new login URL for the oauth2Login() element by using theloginPage() method:

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

Here, we've set up our login URL to be /oauth_login.

Next, let's define a LoginController with a method that maps to this URL:

@Controller public class LoginController { private static String authorizationRequestBaseUri = "oauth2/authorization"; Map oauth2AuthenticationUrls = new HashMap(); @Autowired private ClientRegistrationRepository clientRegistrationRepository; @GetMapping("/oauth_login") public String getLoginPage(Model model) { // ... return "oauth_login"; } }

This method has to send a map of the clients available and their authorization endpoints to the view, which we'll obtain from the ClientRegistrationRepository bean:

public String getLoginPage(Model model) { 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(), authorizationRequestBaseUri + "/" + registration.getRegistrationId())); model.addAttribute("urls", oauth2AuthenticationUrls); return "oauth_login"; }

Finally, we need to define our oauth_login.html page:

Login with:

Client

This is a simple HTML page that displays links to authenticate with each client.

After adding some styling to it, we can change the look of the login page:

5.2. Custom Authentication Success and Failure Behavior

We can control the post-authentication behavior by using different methods:

  • defaultSuccessUrl() and failureUrl() – to redirect the user to a given URL
  • successHandler() and failureHandler() – to execute custom logic following the authentication process

Let's see how we can set custom URL's to redirect the user to:

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

If the user visited a secured page before authenticating, they will be redirected to that page after logging in; otherwise, they will be redirected to /loginSuccess.

If we want the user to always be sent to the /loginSuccess URL regardless if they were on a secured page before or not, we can use the method defaultSuccessUrl(“/loginSuccess”, true).

To use a custom handler, we would have to create a class that implements the AuthenticationSuccessHandler or AuthenticationFailureHandler interfaces, override the inherited methods, then set the beans using the successHandler() and failureHandler() methods.

5.3. Custom Authorization Endpoint

The authorization endpoint is the endpoint that Spring Security uses to trigger an authorization request to the external server.

First, let's set new properties for the authorization endpoint:

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

Here, we've modified the baseUri to /oauth2/authorize-client instead of the default /oauth2/authorization. We're also explicitly setting an authorizationRequestRepository() bean that we have to define:

@Bean public AuthorizationRequestRepository authorizationRequestRepository() { return new HttpSessionOAuth2AuthorizationRequestRepository(); }

In our example, we've used the Spring-provided implementation for our bean, but we could also provide a custom one.

5.4. Custom Token Endpoint

The token endpoint processes access tokens.

Let's explicitly configure the tokenEndpoint()with the default response client implementation:

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

And here's the response client bean:

@Bean public OAuth2AccessTokenResponseClient accessTokenResponseClient() { return new NimbusAuthorizationCodeTokenResponseClient(); }

This configuration is the same as the default one and is using the Spring implementation which is based on exchanging an authorization code with the provider.

Of course, we could also substitute a custom response client.

5.5. Custom Redirection Endpoint

This is the endpoint to redirect to after authentication with the external provider.

Let's see how we can change the baseUri for the redirection endpoint:

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

The default URI is login/oauth2/code.

Note that if we change it, we also have to update the redirectUriTemplate property of each ClientRegistration and add the new URI as an authorized redirect URI for each client.

5.6. Custom User Information Endpoint

The user info endpoint is the location we can leverage to obtain user information.

We can customize this endpoint using the userInfoEndpoint() method. For this, we can use methods such as userService() and customUserType() to modify the way user information is retrieved.

6. Accessing User Information

A common task we may want to achieve is finding information about the logged-in user. For this, we can make a request to the user information endpoint.

First, we'll have to get the client corresponding to the current user token:

@Autowired private OAuth2AuthorizedClientService authorizedClientService; @GetMapping("/loginSuccess") public String getLoginInfo(Model model, OAuth2AuthenticationToken authentication) { OAuth2AuthorizedClient client = authorizedClientService .loadAuthorizedClient( authentication.getAuthorizedClientRegistrationId(), authentication.getName()); //... return "loginSuccess"; }

Next, we'll send a request to the client's user info endpoint and retrieve the userAttributes Map:

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

Durch das Hinzufügen des Namens Eigenschaft als Modell Attribut, können wir es in der Anzeige loginSuccess Ansicht als eine willkommene Nachricht an den Benutzer:

Neben dem Namen enthält die userAttributes Map auch Eigenschaften wie E-Mail, Familienname, Bild, Gebietsschema.

7. Fazit

In diesem Artikel haben wir gesehen, wie wir das oauth2Login () -Element in Spring Security verwenden können, um uns bei verschiedenen Anbietern wie Google und Facebook zu authentifizieren. Wir haben auch einige gängige Szenarien zum Anpassen dieses Prozesses durchlaufen.

Den vollständigen Quellcode der Beispiele finden Sie auf GitHub.