Spring Security 5 - OAuth2-Anmeldung

Spring Security 5 - OAuth2-Anmeldung

1. Überblick

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

In diesem Artikel werdenwe’ll explore some of the various configuration options available for the oauth2Login() element.

2. Maven-Abhängigkeiten

Zusätzlich zu den Standardabhängigkeiten Spring und Spring Security müssen wir auch die Abhängigkeitenspring-security-oauth2-client undspring-security-oauth2-jose hinzufügen:


    org.springframework.security
    spring-security-oauth2-client


   org.springframework.security
   spring-security-oauth2-jose

In unserem Beispiel werden Abhängigkeiten vom übergeordneten Spring Boot-Starter verwaltet, Version2.x,, die Version5.x der Spring Security-Artefakte entspricht.

3. Clients einrichten

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

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

3.1. Abrufen von Client-Anmeldeinformationen

Um Client-Anmeldeinformationen für die Google OAuth2-Authentifizierung zu erhalten, gehen Sie zuGoogle API Console - Abschnitt „Anmeldeinformationen“.

Hier erstellen wir Anmeldeinformationen vom Typ "OAuth2-Client-ID" für unsere Webanwendung. Dies hat zur Folge, dass Google eine Kunden-ID und ein Geheimnis für uns erstellt.

Wir müssen auch einen autorisierten Weiterleitungs-URI in der Google-Konsole konfigurieren. Dies ist der Pfad, zu dem Nutzer nach der erfolgreichen Anmeldung bei Google weitergeleitet werden.

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

http://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 Website vonFacebook for Developersregistrieren und den entsprechenden URI als "gültigen OAuth-Umleitungs-URI" einrichten:

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

3.3. Sicherheitskonfiguration

Als Nächstes müssen wir der Dateiapplication.propertiesClient-Anmeldeinformationen hinzufügen. The Spring Security properties are prefixed with “spring.security.oauth2.client.registration” followed by the client name, then the name of the client property:

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=

Adding these properties for at least one client will enable the Oauth2ClientAutoConfiguration class, das alle erforderlichen Bohnen erstellt.

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

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
         .anyRequest().authenticated()
         .and()
         .oauth2Login();
    }
}

Hier können wir sehen, dass dasoauth2Login()-Element auf ähnliche Weise wie bereits bekanntehttpBasic()- undformLogin()-Elemente verwendet wird.

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

image

3.4. Andere Kunden

Note that in addition to Google and Facebook, the Spring Security project also contains default configurations for GitHub and Okta. 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. Herezeigt einen Blick auf die Standardkonfigurationen in Spring Security, um eine Vorstellung von den erforderlichen Eigenschaften zu erhalten.

4. Einrichtung in einem Nicht-Boot-Projekt

4.1. Erstellen einerClientRegistrationRepository Bean

If we’re not working with a Spring Boot application, we’ll need to define a ClientRegistrationRepository bean, 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 einInMemoryClientRegistrationRepository mit einer Liste vonClientRegistration Objekten.

4.2. ClientRegistration Objekte erstellen

Sehen wir uns diegetRegistration()-Methode 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 ähnlichenapplication.properties-Datei und verwenden dann die bereits in Spring Security definierteCommonOauth2Provider-Aufzählung für die restlichen Client-Eigenschaften für Google- und Facebook-Clients.

JedeClientRegistration-Instanz entspricht einem Client.

4.3. ClientRegistrationRepository registrieren

Schließlich müssen wir eineOAuth2AuthorizedClientService-Bean basierend auf derClientRegistrationRepository-Bean erstellen und beide mit demoauth2Login()-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());
}

Wie hier gezeigt,we can use the clientRegistrationRepository() method of oauth2Login() to register a custom registration repository.

Wir müssen auch eine benutzerdefinierte Anmeldeseite definieren, da diese nicht mehr automatisch generiert wird. Weitere Informationen hierzu finden Sie im nächsten Abschnitt.

Fahren wir mit der weiteren Anpassung unseres Anmeldevorgangs fort.

5. Anpassen vonoauth2Login()

Es gibt mehrere Elemente, die der OAuth 2-Prozess verwendet und die wir mit den Methoden vonoauth2Login()anpassen können.

Beachten Sie, dass alle diese Elemente in Spring Boot Standardkonfigurationen haben und keine explizite Konfiguration erforderlich ist.

Mal sehen, wie wir diese in unserer Konfiguration anpassen können.

5.1. Benutzerdefinierte Anmeldeseite

Obwohl Spring Boot eine Standard-Anmeldeseite für uns generiert, möchten wir normalerweise unsere eigene angepasste Seite definieren.

Beginnen wir mit der Konfiguration einer neuen Anmelde-URL für dasoauth2Login()-Element mithilfe von loginPage() Methode:

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

Hier haben wir unsere Anmelde-URL auf/oauth_login. eingestellt

Als Nächstes definieren wir einLoginController mit einer Methode, die dieser URL zugeordnet ist:

@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, die wir aus derClientRegistrationRepository-Bohne erhalten:

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";
}

Schließlich müssen wir unsereoauth_login.html-Seite definieren:

Login with:

Client

Dies ist eine einfache HTML-Seite, auf der Links zur Authentifizierung bei jedem Client angezeigt werden.

Nach dem Hinzufügen von Styling können wir eine viel schönere Anmeldeseite haben:

image

5.2. Erfolgreiches und fehlgeschlagenes Verhalten bei der benutzerdefinierten Authentifizierung

Wir können das Verhalten nach der Authentifizierung mithilfe verschiedener Methoden steuern:

  • defaultSuccessUrl() undfailureUrl() - um den Benutzer zu einer bestimmten URL umzuleiten

  • successHandler() undfailureHandler() - zum Ausführen einer benutzerdefinierten Logik nach dem Authentifizierungsprozess

Mal sehen, wie wir benutzerdefinierte URLs festlegen können, um den Benutzer umzuleiten:

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

Wenn der Benutzer vor der Authentifizierung eine gesicherte Seite besucht hat, wird er nach dem Anmelden auf diese Seite umgeleitet. Andernfalls werden sie zu/loginSuccess. umgeleitet

Wenn der Benutzer immer an die URL von/loginSuccessgesendet werden soll, unabhängig davon, ob er sich zuvor auf einer gesicherten Seite befand oder nicht, können wir die MethodedefaultSuccessUrl(“/loginSuccess”, true) verwenden.

Um einen benutzerdefinierten Handler zu verwenden, müssten wir eine Klasse erstellen, die die SchnittstellenAuthenticationSuccessHandler oderAuthenticationFailureHandler implementiert, die geerbten Methoden überschreibt und dann die Beans mit den MethodensuccessHandler() und failHandler () festlegt .

5.3. Benutzerdefinierter Autorisierungsendpunkt

Der Autorisierungsendpunkt ist der Endpunkt, den Spring Security verwendet, um eine Autorisierungsanforderung an den externen Server auszulösen.

Erstenslet’s set new properties for the authorization endpoint:

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

Hier haben wir diebaseUri in/oauth2/authorize-client anstelle der Standard/oauth2/authorization. geändert. Wir setzen auch explizit eineauthorizationRequestRepository()-Bean, die wir definieren müssen:

@Bean
public AuthorizationRequestRepository
  authorizationRequestRepository() {

    return new HttpSessionOAuth2AuthorizationRequestRepository();
}

In unserem Beispiel haben wir die von Spring bereitgestellte Implementierung für unsere Bean verwendet, aber wir könnten auch eine benutzerdefinierte Implementierung bereitstellen.

5.4. Benutzerdefinierter Token-Endpunkt

Das Tokenendpointverarbeitet Zugriffstoken.

Let’s explicitly configure the tokenEndpoint() mit der Standardantwort-Client-Implementierung:

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

Und hier ist die Antwort-Client-Bean:

@Bean
public OAuth2AccessTokenResponseClient
  accessTokenResponseClient() {

    return new NimbusAuthorizationCodeTokenResponseClient();
}

Diese Konfiguration ist dieselbe wie die Standardkonfiguration und verwendet die Spring-Implementierung, die auf dem Austausch eines Autorisierungscodes mit dem Anbieter basiert.

Natürlich können wir auch einen benutzerdefinierten Antwortclient ersetzen.

5.5. Benutzerdefinierter Umleitungsendpunkt

Dies ist der Endpunkt, zu dem nach der Authentifizierung beim externen Anbieter umgeleitet werden soll.

Mal sehen, wie wir diebaseUri für den Umleitungsendpunkt ändern können:

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

Der Standard-URI istlogin/oauth2/code.

Beachten Sie, dass wir bei einer Änderung auch die EigenschaftredirectUriTemplatejedesClientRegistrationaktualisieren und den neuen URI als autorisierten Umleitungs-URI für jeden Client hinzufügen müssen.

5.6. Endpunkt für benutzerdefinierte Benutzerinformationen

Der Endpunkt für Benutzerinformationen ist der Ort, an dem wir Benutzerinformationen abrufen können.

We can customize this endpoint using the userInfoEndpoint() method. Zu diesem Zweck können wir Methoden wieuserService() undcustomUserType() verwenden, um die Art und Weise zu ändern, in der Benutzerinformationen abgerufen werden.

6. Zugriff auf Benutzerinformationen

Eine häufige Aufgabe, die wir möglicherweise ausführen möchten, ist das Auffinden von Informationen über den angemeldeten Benutzer. Dazuwe can make a request to the user information endpoint.

Zuerst müssen wir den Client erhalten, der dem aktuellen Benutzertoken entspricht:

@Autowired
private OAuth2AuthorizedClientService authorizedClientService;

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

Als Nächstes senden wir eine Anfrage an den Benutzerinformationsendpunkt des Clients und rufen dieuserAttributes Map:ab

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 Hinzufügen der Eigenschaftname als AttributModel können wir sie in der AnsichtloginSuccessals Willkommensnachricht für den Benutzer anzeigen:

image

Nebenname, enthältuserAttributes Map auch Eigenschaften wieemail, family_name,picture, locale.

7. Fazit

In diesem Artikel haben wir gesehen, wie wir das Elementoauth2Login()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.

Der vollständige Quellcode der Beispiele istover on GitHub.