Authentifizierung zu deiner Go-Webanwendung hinzufügen
Diese Anleitung zeigt dir, wie du Logto in deine Go-Webanwendung integrierst.
- Die folgende Demonstration basiert auf dem Gin Web Framework. Du kannst Logto auch in andere Frameworks integrieren, indem du die gleichen Schritte befolgst.
- Das Go-Beispielprojekt ist in unserem Go SDK-Repo verfügbar.
Voraussetzungen
- Ein Logto Cloud Konto oder ein selbst gehostetes Logto.
- Eine Logto traditionelle Webanwendung erstellt.
Installation
Führe im Stammverzeichnis des Projekts aus:
go get github.com/logto-io/go
Füge das github.com/logto-io/go/client
-Paket zu deinem Anwendungscode hinzu:
// main.go
package main
import (
"github.com/gin-gonic/gin"
// Abhängigkeit hinzufügen
"github.com/logto-io/go/client"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello Logto!")
})
router.Run(":3000")
}
Integration
Erstelle einen Sitzungs-Speicher
In traditionellen Webanwendungen werden die Benutzer-Authentifizierungsinformationen in der Benutzersitzung gespeichert.
Das Logto SDK bietet eine Storage
-Schnittstelle, mit der du einen Storage
-Adapter basierend auf deinem Web-Framework implementieren kannst, sodass das Logto SDK die Benutzer-Authentifizierungsinformationen in der Sitzung speichern kann.
Wir empfehlen NICHT die Verwendung von cookie-basierten Sitzungen, da die von Logto gespeicherten Benutzer-Authentifizierungsinformationen die Cookie-Größenbeschränkung überschreiten können. In diesem Beispiel verwenden wir speicherbasierte Sitzungen. Du kannst Redis, MongoDB und andere Technologien in der Produktion verwenden, um Sitzungen nach Bedarf zu speichern.
Der Storage
-Typ im Logto SDK sieht wie folgt aus:
package client
type Storage interface {
GetItem(key string) string
SetItem(key, value string)
}
Wir verwenden die github.com/gin-contrib/sessions Middleware als Beispiel, um diesen Prozess zu demonstrieren.
Wende die Middleware auf die Anwendung an, damit wir die Benutzersitzung durch den Benutzeranforderungskontext im Routen-Handler erhalten können:
package main
import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/memstore"
"github.com/gin-gonic/gin"
"github.com/logto-io/go/client"
)
func main() {
router := gin.Default()
// Wir verwenden speicherbasierte Sitzungen in diesem Beispiel
store := memstore.NewStore([]byte("dein Sitzungsschlüssel"))
router.Use(sessions.Sessions("logto-session", store))
router.GET("/", func(ctx *gin.Context) {
// Benutzersitzung abrufen
session := sessions.Default(ctx)
// ...
ctx.String(200, "Hallo Logto!")
})
router.Run(":3000")
}
Erstelle eine session_storage.go
-Datei, definiere eine SessionStorage
und implementiere die Storage
-Schnittstellen des Logto SDK:
package main
import (
"github.com/gin-contrib/sessions"
)
type SessionStorage struct {
session sessions.Session
}
func (storage *SessionStorage) GetItem(key string) string {
value := storage.session.Get(key)
if value == nil {
return ""
}
return value.(string)
}
func (storage *SessionStorage) SetItem(key, value string) {
storage.session.Set(key, value)
storage.session.Save()
}
Jetzt kannst du im Routen-Handler einen Sitzungs-Speicher für Logto erstellen:
session := sessions.Default(ctx)
sessionStorage := &SessionStorage{session: session}
LogtoClient initialisieren
Zuerst erstelle eine Logto-Konfiguration:
func main() {
// ...
logtoConfig := &client.LogtoConfig{
Endpoint: "<your-logto-endpoint>", // Z.B. http://localhost:3001
AppId: "<your-application-id>",
AppSecret: "<your-application-secret>",
}
// ...
}
Du kannst das "App Secret" auf der Anwendungsdetailseite in der Admin-Konsole finden und kopieren:
Dann kannst du einen LogtoClient
für jede Benutzeranfrage mit der obigen Logto-Konfiguration erstellen:
func main() {
// ...
router.GET("/", func(ctx *gin.Context) {
// LogtoClient erstellen
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// Verwende Logto, um den Inhalt der Startseite zu steuern
authState := "Du bist nicht auf dieser Website angemeldet. :("
if logtoClient.IsAuthenticated() {
authState = "Du bist auf dieser Website angemeldet! :)"
}
homePage := `<h1>Hallo Logto</h1>` +
"<div>" + authState + "</div>"
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// ...
}
Konfiguriere deine App
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.
In den folgenden Code-Snippets gehen wir davon aus, dass deine App unter http://localhost:3000/
läuft.
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.
Redirect behandeln
Wenn sich der Benutzer erfolgreich auf der Logto-Anmeldeseite anmeldet, wird Logto den Benutzer zur Redirect-URI weiterleiten.
Da die Redirect-URI http://localhost:3000/callback
ist, fügen wir die Route /callback
hinzu, um den Callback nach der Anmeldung zu bearbeiten.
func main() {
// ...
// Füge eine Route hinzu, um Anmelde-Callback-Anfragen zu bearbeiten
router.GET("/callback", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// Die Anmelde-Callback-Anfrage wird von Logto bearbeitet
err := logtoClient.HandleSignInCallback(ctx.Request)
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
// Springe zur Seite, die vom Entwickler angegeben wurde.
// In diesem Beispiel wird der Benutzer zur Startseite zurückgeführt.
ctx.Redirect(http.StatusTemporaryRedirect, "/")
})
// ...
}
Anmelderoute implementieren
Nachdem die Redirect-URI konfiguriert wurde, fügen wir eine sign-in
-Route hinzu, um die Anmeldeanfrage zu bearbeiten, und fügen auch einen Anmeldelink auf der Startseite hinzu:
func main() {
// ...
// Füge einen Link hinzu, um eine Anmeldeanfrage auf der Startseite durchzuführen
router.GET("/", func(ctx *gin.Context) {
// ...
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>" +
// Link hinzufügen
`<div><a href="/sign-in">Sign In</a></div>`
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// Füge eine Route hinzu, um Anmeldeanfragen zu bearbeiten
router.GET("/sign-in", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// Die Anmeldeanfrage wird von Logto bearbeitet.
// Der Benutzer wird zur Redirect-URI weitergeleitet, nachdem er sich angemeldet hat.
signInUri, err := logtoClient.SignIn("http://localhost:3000/callback")
if err != nil {
ctx.String(http.StatusInternalServerError, err.Error())
return
}
// Leite den Benutzer zur Logto-Anmeldeseite weiter.
ctx.Redirect(http.StatusTemporaryRedirect, signInUri)
})
// ...
}
Jetzt, wenn dein Benutzer http://localhost:3000/sign-in
besucht, wird der Benutzer zur Logto-Anmeldeseite weitergeleitet.
Abmelderoute implementieren
Ähnlich wie beim Anmeldefluss wird Logto den Benutzer nach dem Abmelden zur Post-Sign-out-Redirect-URI weiterleiten.
Fügen wir nun die sign-out
-Route hinzu, um die Abmeldeanfrage zu bearbeiten, und fügen wir auch einen Abmeldelink auf der Startseite hinzu:
func main() {
// ...
// Füge einen Link hinzu, um eine Abmeldeanfrage auf der Startseite durchzuführen
router.GET("/", func(ctx *gin.Context) {
// ...
homePage := `<h1>Hello Logto</h1>` +
"<div>" + authState + "</div>" +
`<div><a href="/sign-in">Sign In</a></div>` +
// Link hinzufügen
`<div><a href="/sign-out">Sign Out</a></div>`
ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(homePage))
})
// Füge eine Route hinzu, um Abmeldeanfragen zu bearbeiten
router.GET("/sign-out", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(
logtoConfig,
&SessionStorage{session: session},
)
// Die Abmeldeanfrage wird von Logto bearbeitet.
// Der Benutzer wird zur Post-Sign-out-Redirect-URI weitergeleitet, nachdem er abgemeldet wurde.
signOutUri, signOutErr := logtoClient.SignOut("http://localhost:3000")
if signOutErr != nil {
ctx.String(http.StatusOK, signOutErr.Error())
return
}
ctx.Redirect(http.StatusTemporaryRedirect, signOutUri)
})
// ...
}
Nachdem der Benutzer eine Abmeldeanfrage gestellt hat, wird Logto alle Benutzer-Authentifizierungsinformationen in der Sitzung löschen.
Checkpoint: Teste deine Anwendung
Jetzt kannst du deine Anwendung testen:
- Starte deine Anwendung, du wirst den Anmeldebutton sehen.
- Klicke auf den Anmeldebutton, das SDK wird den Anmeldeprozess initiieren und dich zur Logto-Anmeldeseite weiterleiten.
- Nachdem du dich angemeldet hast, wirst du zurück zu deiner Anwendung geleitet und siehst den Abmeldebutton.
- Klicke auf den Abmeldebutton, um den lokalen Speicher zu leeren und dich abzumelden.
Benutzerinformationen abrufen
Benutzerinformationen anzeigen
Um die Informationen des Benutzers anzuzeigen, kannst du die Methode client.GetIdTokenClaims
verwenden. Füge zum Beispiel eine Route hinzu:
func main() {
//...
router.GET("/user-id-token-claims", func(ctx *gin.Context) {
session := sessions.Default(ctx)
logtoClient := client.NewLogtoClient(logtoConfig, &SessionStorage{session: session})
idTokenClaims, err := logtoClient.GetIdTokenClaims()
if err != nil {
ctx.String(http.StatusOK, err.Error())
}
ctx.JSON(http.StatusOK, idTokenClaims)
})
}
Zusätzliche Ansprüche anfordern
Möglicherweise fehlen einige Benutzerinformationen im zurückgegebenen Objekt von client.GetIdTokenClaims()
. 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 Berechtigungen anzufordern, kannst du die Berechtigungen an das LogtoConfig
-Objekt übergeben. Zum Beispiel:
logtoConfig := &client.LogtoConfig{
// ...andere Konfigurationen
Scopes: []string{"email", "phone"},
}
Dann kannst du auf die zusätzlichen Ansprüche im Rückgabewert von client.GetIdTokenClaims()
zugreifen:
idTokenClaims, error := client.GetIdTokenClaims()
// Jetzt kannst du auf zusätzliche Ansprüche `claims.email`, `claims.phone` usw. zugreifen.
Ansprüche, die Netzwerk-Anfragen benötigen
Um das ID-Token nicht aufzublähen, erfordern einige Ansprüche Netzwerk-Anfragen, um abgerufen zu werden. Zum Beispiel ist der custom_data
Anspruch nicht im Benutzerobjekt enthalten, selbst wenn er in den Berechtigungen angefordert wird. Um auf diese Ansprüche zuzugreifen, kannst du die client.FetchUserInfo()
Methode verwenden:
userInfo, error := client.FetchUserInfo()
// Jetzt kannst du auf den Anspruch `userInfo.custom_data` zugreifen.
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.
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.
API-Ressourcen und Organisationen
Wir empfehlen, zuerst 🔐 Rollenbasierte Zugangskontrolle (RBAC) zu lesen, um die grundlegenden Konzepte von Logto RBAC zu verstehen und wie man API-Ressourcen richtig einrichtet.
Logto-Client konfigurieren
Sobald du die API-Ressourcen eingerichtet hast, kannst du sie bei der Konfiguration von Logto in deiner App hinzufügen:
logtoConfig := &client.LogtoConfig{
// ...other configs
Resources: []string{"https://shopping.your-app.com/api", "https://store.your-app.com/api"},
}
Jede API-Ressource hat ihre eigenen Berechtigungen (Berechtigungen).
Zum Beispiel hat die Ressource https://shopping.your-app.com/api
die Berechtigungen shopping:read
und shopping:write
, und die Ressource https://store.your-app.com/api
hat die Berechtigungen store:read
und store:write
.
Um diese Berechtigungen anzufordern, kannst du sie bei der Konfiguration von Logto in deiner App hinzufügen:
logtoConfig := &client.LogtoConfig{
// ...other configs
Scopes: []string{"shopping:read", "shopping:write", "store:read", "store:write"},
Resources: []string{"https://shopping.your-app.com/api", "https://store.your-app.com/api"},
}
Du wirst bemerken, dass Berechtigungen separat von API-Ressourcen definiert sind. Dies liegt daran, dass Resource Indicators for OAuth 2.0 spezifiziert, dass die endgültigen Berechtigungen für die Anfrage das kartesische Produkt aller Berechtigungen bei allen Zielservices sein werden.
Somit können im obigen Fall die Berechtigungen aus der Definition in Logto vereinfacht werden, beide API-Ressourcen können read
und write
Berechtigungen ohne Präfix haben. Dann, in der Logto-Konfiguration:
logtoConfig := &client.LogtoConfig{
// ...other configs
Scopes: []string{"read", "write"},
Resources: []string{"https://shopping.your-app.com/api", "https://store.your-app.com/api"},
}
Für jede API-Ressource wird sowohl read
als auch write
Berechtigungen angefordert.
Es ist in Ordnung, Berechtigungen anzufordern, die in den API-Ressourcen nicht definiert sind. Zum Beispiel kannst du die Berechtigung email
anfordern, auch wenn die API-Ressourcen die Berechtigung email
nicht verfügbar haben. Nicht verfügbare Berechtigungen werden sicher ignoriert.
Nach der erfolgreichen Anmeldung wird Logto die entsprechenden Berechtigungen an API-Ressourcen gemäß den Rollen des Benutzers ausstellen.
Zugangstoken für die API-Ressource abrufen
Um das Zugangstoken für eine spezifische API-Ressource abzurufen, kannst du die Methode GetAccessToken
verwenden:
accessToken, error := logtoClient.GetAccessToken("https://shopping.your-app.com/api")
Diese Methode gibt ein JWT-Zugangstoken zurück, das verwendet werden kann, um auf die API-Ressource zuzugreifen, wenn der Benutzer die entsprechenden Berechtigungen hat. Wenn das aktuell zwischengespeicherte Zugangstoken abgelaufen ist, versucht diese Methode automatisch, ein Auffrischungstoken zu verwenden, um ein neues Zugangstoken zu erhalten.
Organisationstokens abrufen
Wenn Organisationen neu für dich sind, lies bitte 🏢 Organisationen (Multi-Tenancy), um loszulegen.
Du musst die Berechtigung core.UserScopeOrganizations
hinzufügen, wenn du den Logto-Client konfigurierst:
logtoConfig := &client.LogtoConfig{
// ...other configs
Scopes: []string{core.UserScopeOrganizations},
}
Sobald der Benutzer angemeldet ist, kannst du das Organisationstoken für den Benutzer abrufen:
// Ersetze den Parameter durch eine gültige Organisations-ID.
// Gültige Organisations-IDs für den Benutzer können im ID-Token-Anspruch `organizations` gefunden werden.
accessToken, error := logtoClient.GetOrganizationToken("organization-id")
// oder
accessTokenClaims, error := logtoClient.GetOrganizationTokenClaims("organization-id")
Organisations-API-Ressourcen
Um ein Zugangstoken für eine API-Ressource in einer Organisation abzurufen, kannst du die Methode getAccessToken
mit sowohl der API-Ressource als auch der Organisations-ID als Parameter verwenden:
accessToken, error := client.GetAccessToken(
'https://shopping.your-app.com/api',
organizationId
);