Authentifizierung zu deiner Java Spring Boot-Anwendung hinzufügen
Diese Anleitung zeigt dir, wie du Logto in deine Java Spring Boot-Anwendung integrierst.
- Du findest den Beispielcode für diese Anleitung in unserem spring-boot-sample GitHub-Repository.
- Es ist kein offizielles SDK erforderlich, um Logto mit deiner Java Spring Boot-Anwendung zu integrieren. Wir werden die Bibliotheken Spring Security und Spring Security OAuth2 verwenden, um den OIDC-Authentifizierungsfluss mit Logto zu handhaben.
Voraussetzungen
- Ein Logto Cloud Konto oder ein selbst gehostetes Logto.
- Unser Beispielcode wurde mit dem Spring Boot securing web starter erstellt. Folge den Anweisungen, um eine neue Webanwendung zu starten, wenn du noch keine hast.
- In dieser Anleitung verwenden wir die Bibliotheken Spring Security und Spring Security OAuth2, um den OIDC-Authentifizierungsfluss mit Logto zu handhaben. Bitte gehe die offizielle Dokumentation durch, um die Konzepte zu verstehen.
Konfiguriere deine Java Spring Boot-Anwendung
Abhängigkeiten hinzufügen
Für gradle Benutzer, füge die folgenden Abhängigkeiten zu deiner build.gradle
Datei hinzu:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}
Für maven Benutzer, füge die folgenden Abhängigkeiten zu deiner pom.xml
Datei hinzu:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
OAuth2 Client-Konfiguration
Registriere eine neue Java Spring Boot
Anwendung in der Logto-Konsole und erhalte die Client-Anmeldeinformationen und IdP-Konfigurationen für deine Webanwendung.
Füge die folgende Konfiguration zu deiner application.properties
Datei hinzu:
spring.security.oauth2.client.registration.logto.client-name=logto
spring.security.oauth2.client.registration.logto.client-id={{YOUR_CLIENT_ID}}
spring.security.oauth2.client.registration.logto.client-secret={{YOUR_CLIENT_ID}}
spring.security.oauth2.client.registration.logto.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.logto.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access
spring.security.oauth2.client.registration.logto.provider=logto
spring.security.oauth2.client.provider.logto.issuer-uri={{LOGTO_ENDPOINT}}/oidc
spring.security.oauth2.client.provider.logto.authorization-uri={{LOGTO_ENDPOINT}}/oidc/auth
spring.security.oauth2.client.provider.logto.jwk-set-uri={{LOGTO_ENDPOINT}}/oidc/jwks
Implementierung
Bevor wir in die Details eintauchen, hier ein kurzer Überblick über die Endbenutzererfahrung. Der Anmeldeprozess kann wie folgt vereinfacht werden:
- Deine App ruft die Anmeldemethode auf.
- Der Benutzer wird zur Logto-Anmeldeseite umgeleitet. Bei nativen Apps wird der Systembrowser geöffnet.
- Der Benutzer meldet sich an und wird zurück zu deiner App umgeleitet (konfiguriert als die Redirect-URI).
Bezüglich der umleitungsbasierten Anmeldung
- Dieser Authentifizierungsprozess folgt dem OpenID Connect (OIDC) Protokoll, und Logto erzwingt strenge Sicherheitsmaßnahmen, um die Benutzeranmeldung zu schützen.
- Wenn du mehrere Apps hast, kannst du denselben Identitätsanbieter (Logto) verwenden. Sobald sich der Benutzer bei einer App anmeldet, wird Logto den Anmeldeprozess automatisch abschließen, wenn der Benutzer auf eine andere App zugreift.
Um mehr über die Gründe und Vorteile der umleitungsbasierten Anmeldung zu erfahren, siehe Logto-Anmeldeerfahrung erklärt.
Um Benutzer nach der Anmeldung zurück zu deiner Anwendung umzuleiten, musst du die Redirect-URI mit der Eigenschaft client.registration.logto.redirect-uri
im vorherigen Schritt festlegen.
Redirect-URIs konfigurieren
Wechsle zur Anwendungsdetailseite der Logto-Konsole. Füge eine Redirect-URI http://localhost:3000/callback
hinzu.
Genau wie beim Anmelden sollten Benutzer zu Logto weitergeleitet werden, um sich von der gemeinsamen Sitzung abzumelden. Sobald dies abgeschlossen ist, wäre es ideal, den Benutzer zurück zu deiner Website zu leiten. Füge zum Beispiel http://localhost:3000/
als Redirect-URI nach dem Abmelden hinzu.
Klicke dann auf "Speichern", um die Änderungen zu speichern.
Implementiere die WebSecurityConfig
Erstelle eine neue Klasse WebSecurityConfig
in deinem Projekt
Die Klasse WebSecurityConfig
wird verwendet, um die Sicherheitseinstellungen für deine Anwendung zu konfigurieren. Sie ist die Schlüsselklasse, die den Authentifizierungs- und Autorisierungsfluss handhaben wird. Bitte überprüfe die Spring Security Dokumentation für weitere Details.
package com.example.securingweb;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
// ...
}
Erstelle einen idTokenDecoderFactory
Bean
Dies ist erforderlich, da Logto ES384
als Standardalgorithmus verwendet. Wir müssen den Standard OidcIdTokenDecoderFactory
überschreiben, um denselben Algorithmus zu verwenden.
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
public class WebSecurityConfig {
// ...
@Bean
public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> SignatureAlgorithm.ES384);
return idTokenDecoderFactory;
}
}
Erstelle eine LoginSuccessHandler-Klasse, um das erfolgreiche Anmeldeereignis zu handhaben
Wir leiten den Benutzer nach einer erfolgreichen Anmeldung zur /user
Seite weiter.
package com.example.securingweb;
import java.io.IOException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class CustomSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/user");
}
}
Erstelle eine LogoutSuccessHandler-Klasse, um das erfolgreiche Abmeldeereignis zu handhaben
Lösche die Sitzung und leite den Benutzer zur Startseite weiter.
package com.example.securingweb;
import java.io.IOException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
public class CustomLogoutHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
HttpSession session = request.getSession();
if (session != null) {
session.invalidate();
}
response.sendRedirect("/home");
}
}
Aktualisiere die WebSecurityConfig
Klasse mit einer securityFilterChain
securityFilterChain ist eine Kette von Filtern, die für die Verarbeitung der eingehenden Anfragen und Antworten verantwortlich sind.
Wir werden die securityFilterChain
konfigurieren, um den Zugriff auf die Startseite zu erlauben und für alle anderen Anfragen eine Authentifizierung zu verlangen. Verwende den CustomSuccessHandler
und CustomLogoutHandler
, um die Anmelde- und Abmeldeereignisse zu handhaben.
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
public class WebSecurityConfig {
// ...
@Bean
public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers("/", "/home").permitAll() // Erlaube den Zugriff auf die Startseite
.anyRequest().authenticated() // Alle anderen Anfragen erfordern Authentifizierung
)
.oauth2Login(oauth2Login ->
oauth2Login
.successHandler(new CustomSuccessHandler())
)
.logout(logout ->
logout
.logoutSuccessHandler(new CustomLogoutHandler())
);
return http.build();
}
}
Erstelle eine Startseite
(Du kannst diesen Schritt überspringen, wenn du bereits eine Startseite in deinem Projekt hast)
package com.example.securingweb;
import java.security.Principal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping({ "/", "/home" })
public String home(Principal principal) {
return principal != null ? "redirect:/user" : "home";
}
}
Dieser Controller leitet den Benutzer zur Benutzerseite weiter, wenn der Benutzer authentifiziert ist, andernfalls wird die Startseite angezeigt. Füge einen Anmeldelink zur Startseite hinzu.
<body>
<h1>Willkommen!</h1>
<p><a th:href="@{/oauth2/authorization/logto}">Mit Logto anmelden</a></p>
</body>
Erstelle eine Benutzerseite
Erstelle einen neuen Controller, um die Benutzerseite zu handhaben:
package com.example.securingweb;
import java.security.Principal;
import java.util.Map;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@GetMapping
public String user(Model model, Principal principal) {
if (principal instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
OAuth2User oauth2User = token.getPrincipal();
Map<String, Object> attributes = oauth2User.getAttributes();
model.addAttribute("username", attributes.get("username"));
model.addAttribute("email", attributes.get("email"));
model.addAttribute("sub", attributes.get("sub"));
}
return "user";
}
}
Sobald der Benutzer authentifiziert ist, werden wir die OAuth2User
Daten aus dem authentifizierten Principal-Objekt abrufen. Bitte siehe OAuth2AuthenticationToken und OAuth2User für weitere Details.
Lese die Benutzerdaten und übergebe sie an das user.html
Template.
<body>
<h1>Benutzerdetails</h1>
<div>
<p>
<div><strong>Name:</strong> <span th:text="${username}"></span></div>
<div><strong>E-Mail:</strong> <span th:text="${email}"></span></div>
<div><strong>ID:</strong> <span th:text="${sub}"></span></div>
</p>
</div>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Abmelden" />
</form>
</body>
Zusätzliche Ansprüche anfordern
Möglicherweise fehlen einige Benutzerinformationen im zurückgegebenen Objekt von principal (OAuth2AuthenticationToken)
. Dies liegt daran, dass OAuth 2.0 und OpenID Connect (OIDC) so konzipiert sind, dass sie dem Prinzip der minimalen Rechte (PoLP) folgen, und Logto auf diesen Standards basiert.
Standardmäßig werden begrenzte Ansprüche zurückgegeben. Wenn du mehr Informationen benötigst, kannst du zusätzliche Berechtigungen anfordern, um auf mehr Ansprüche zuzugreifen.
Ein "Anspruch (Claim)" ist eine Behauptung über ein Subjekt; eine "Berechtigung (Scope)" ist eine Gruppe von Ansprüchen. Im aktuellen Fall ist ein Anspruch ein Informationsstück über den Benutzer.
Hier ist ein nicht-normatives Beispiel für die Beziehung zwischen Berechtigung und Anspruch:
Der "sub"-Anspruch bedeutet "Subjekt", was der eindeutige Identifikator des Benutzers ist (d. h. Benutzer-ID).
Das Logto SDK wird immer drei Berechtigungen anfordern: openid
, profile
und offline_access
.
Um zusätzliche Benutzerinformationen abzurufen, kannst du zusätzliche Berechtigungen zur application.properties
Datei hinzufügen. Zum Beispiel, um die email
, phone
und urn:logto:scope:organizations
Berechtigung anzufordern, füge die folgende Zeile zur application.properties
Datei hinzu:
spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access,email,phone,urn:logto:scope:organizations
Dann kannst du auf die zusätzlichen Ansprüche im OAuth2User
Objekt zugreifen.
Anwendung ausführen und testen
Führe die Anwendung aus und navigiere zu http://localhost:8080
.
- Du wirst die Startseite mit einem Anmeldelink sehen.
- Klicke auf den Link, um dich mit Logto anzumelden.
- Nach erfolgreicher Authentifizierung wirst du zur Benutzerseite mit deinen Benutzerdetails weitergeleitet.
- Klicke auf den Abmeldebutton, um dich abzumelden. Du wirst zurück zur Startseite weitergeleitet.
Berechtigungen und Ansprüche
Logto verwendet OIDC Berechtigungen und Ansprüche Konventionen, um die Berechtigungen und Ansprüche für das Abrufen von Benutzerinformationen aus dem ID-Token und dem OIDC userinfo endpoint zu definieren. Sowohl "Berechtigung (Scope)" als auch "Anspruch (Claim)" sind Begriffe aus den OAuth 2.0 und OpenID Connect (OIDC) Spezifikationen.
Kurz gesagt, wenn du eine Berechtigung anforderst, erhältst du die entsprechenden Ansprüche in den Benutzerinformationen. Zum Beispiel, wenn du die email
Berechtigung anforderst, erhältst du die email
und email_verified
Daten des Benutzers.
Hier ist die Liste der unterstützten Berechtigungen (Scopes) und der entsprechenden Ansprüche (Claims):
openid
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
sub | string | Der eindeutige Identifikator des Benutzers | Nein |
profile
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
name | string | Der vollständige Name des Benutzers | Nein |
username | string | Der Benutzername des Benutzers | Nein |
picture | string | URL des Profilbildes des Endbenutzers. Diese URL MUSS auf eine Bilddatei (zum Beispiel eine PNG-, JPEG- oder GIF-Bilddatei) verweisen und nicht auf eine Webseite, die ein Bild enthält. Beachte, dass diese URL speziell auf ein Profilfoto des Endbenutzers verweisen SOLLTE, das geeignet ist, den Endbenutzer zu beschreiben, und nicht auf ein beliebiges Foto, das vom Endbenutzer aufgenommen wurde. | Nein |
created_at | number | Zeitpunkt, zu dem der Endbenutzer erstellt wurde. Die Zeit wird als Anzahl der Millisekunden seit der Unix-Epoche (1970-01-01T00:00:00Z) dargestellt. | Nein |
updated_at | number | Zeitpunkt, zu dem die Informationen des Endbenutzers zuletzt aktualisiert wurden. Die Zeit wird als Anzahl der Millisekunden seit der Unix-Epoche (1970-01-01T00:00:00Z) dargestellt. | Nein |
Andere Standardansprüche wie family_name
, given_name
, middle_name
, nickname
, preferred_username
, profile
, website
, gender
, birthdate
, zoneinfo
und locale
werden ebenfalls im profile
-Scope enthalten sein, ohne dass der userinfo-Endpunkt angefordert werden muss. Ein Unterschied im Vergleich zu den oben genannten Ansprüchen besteht darin, dass diese Ansprüche nur zurückgegeben werden, wenn ihre Werte nicht leer sind, während die oben genannten Ansprüche null
zurückgeben, wenn die Werte leer sind.
Im Gegensatz zu den Standardansprüchen verwenden die Ansprüche created_at
und updated_at
Millisekunden anstelle von Sekunden.
email
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
string | Die E-Mail-Adresse des Benutzers | Nein | |
email_verified | boolean | Ob die E-Mail-Adresse verifiziert wurde | Nein |
phone
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
phone_number | string | Die Telefonnummer des Benutzers | Nein |
phone_number_verified | boolean | Ob die Telefonnummer verifiziert wurde | Nein |
address
Bitte siehe OpenID Connect Core 1.0 für die Details des Adressanspruchs.
custom_data
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
custom_data | object | Die benutzerdefinierten Daten des Benutzers | Ja |
identities
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
identities | object | Die verknüpften Identitäten des Benutzers | Ja |
sso_identities | array | Die verknüpften SSO-Identitäten des Benutzers | Ja |
urn:logto:scope:organizations
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
organizations | string[] | Die Organisations-IDs, denen der Benutzer angehört | Nein |
organization_data | object[] | Die Organisationsdaten, denen der Benutzer angehört | Ja |
urn:logto:scope:organization_roles
Anspruchsname | Typ | Beschreibung | Benötigt userinfo? |
---|---|---|---|
organization_roles | string[] | Die Organisationsrollen, denen der Benutzer angehört, im Format <organization_id>:<role_name> | Nein |
In Anbetracht der Leistung und der Datengröße bedeutet "Benötigt userinfo?" "Ja", dass der Anspruch nicht im ID-Token angezeigt wird, sondern in der Antwort des userinfo-Endpunkts zurückgegeben wird.
Füge zusätzliche Berechtigungen und Ansprüche in der application.properties
Datei hinzu, um mehr Benutzerinformationen anzufordern. Zum Beispiel, um die urn:logto:scope:organizations
Berechtigung anzufordern, füge die folgende Zeile zur application.properties
Datei hinzu:
spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access,urn:logto:scope:organizations
Benutzerorganisation Ansprüche werden im Autorisierungstoken enthalten sein.