Sichere API-Authentifizierung mit PASETO in Go stärken
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Sicherere APIs mit PASETO in Go erstellen
Die Landschaft der Web-API-Sicherheit entwickelt sich ständig weiter. Seit Jahren sind JSON Web Tokens (JWTs) eine dominierende Kraft und bieten eine kompakte und in sich geschlossene Möglichkeit, Informationen zwischen Parteien zu übertragen. Mit ihrer Verbreitung wuchs jedoch auch das Bewusstsein für ihre potenziellen Fallstricke, insbesondere im Hinblick auf die Komplexität der Implementierung, die Algorithmenflexibilität und die Risiken unsachgemäßer Validierung. Als Entwickler suchen wir ständig nach robusteren und entwicklerfreundlicheren Lösungen, um unsere Anwendungen zu sichern. Dieses Streben hat viele dazu veranlasst, Alternativen zu erforschen, die die Sicherheit vereinfachen und die allgemeine Widerstandsfähigkeit verbessern. Eine vielversprechende Alternative, die an Bedeutung gewinnt, sind Platform-Agnostic Security Tokens (PASETO). Dieser Artikel befasst sich eingehend mit der Implementierung der API-Authentifizierung mit PASETO in Go und zeigt auf, wie dies im Vergleich zu herkömmlichen JWTs einen sichereren und unkomplizierteren Ansatz bieten kann.
Die Säulen sicherer Token verstehen
Bevor wir uns mit der praktischen Implementierung befassen, wollen wir ein grundlegendes Verständnis der Schlüsselkonzepte entwickeln, die unserer Diskussion zugrunde liegen. Auch wenn viele dieser Konzepte aus Ihrer Erfahrung mit JWTs vertraut sein mögen, ist es wichtig, ihre spezifischen Nuancen im PASETO-Ökosystem zu erfassen.
PASETO (Platform-Agnostic Security Tokens): Im Kern ist PASETO eine sichere Alternative zu JWT. Im Gegensatz zu JWTs sind PASETOs standardmäßig sicher (secure by default), d. h. sie integrieren Best Practices für Kryptografie und Token-Struktur, wodurch die Wahrscheinlichkeit häufiger Sicherheitsfehlkonfigurationen verringert wird. Sie werden immer signiert (oder verschlüsselt und signiert), was Manipulationen verhindert und die Authentizität sicherstellt. PASETO gibt es in zwei Hauptvarianten: local
(verschlüsselt) und public
(signiert). Wir konzentrieren uns hauptsächlich auf public
-Token für die API-Authentifizierung, da der Server die Identität des Clients überprüfen muss und nicht unbedingt die Inhalte des Tokens vor dem Client geheim halten muss.
Symmetrische Schlüsselkryptografie: Wird in PASETO local
-Token verwendet, wobei derselbe Schlüssel sowohl für die Ver- als auch für die Entschlüsselung verwendet wird. Dies ist geeignet, wenn der Token-Aussteller und der Konsument dieselbe Einheit sind oder einen geheimen Schlüssel sicher teilen.
Asymmetrische Schlüsselkryptografie: Wird in PASETO public
-Token verwendet (und was wir verwenden werden), bei der ein Paar mathematisch verknüpfter Schlüssel beteiligt ist: ein öffentlicher Schlüssel und ein privater Schlüssel. Der private Schlüssel wird zum Signieren des Tokens verwendet, und der öffentliche Schlüssel wird zum Überprüfen der Signatur verwendet. Dies ist ideal für Szenarien, in denen ein Aussteller Token signiert und mehrere Konsumenten (die nur den öffentlichen Schlüssel besitzen) diese überprüfen müssen, ohne sie fälschen zu können. Dies entspricht perfekt der API-Authentifizierung, bei der Ihr Server Token signiert und Client-Anwendungen diese überprüfen (implizit, indem sie sie zur Überprüfung zurück an den Server senden).
Claims: Sowohl JWT- als auch PASETO-Token enthalten eine Nutzlast, eine Reihe von "Claims" (Ansprüchen), die Aussagen über das Subjekt des Tokens darstellen. Diese sind typischerweise JSON-Objekte und können Standard-Claims wie iss
(Aussteller), sub
(Subjekt), exp
(Ablaufzeit) und benutzerdefinierte anwendungsspezifische Claims enthalten.
Footer: Ein einzigartiges Merkmal von PASETO ist sein optionaler Footer. Dieser ermöglicht das Anhängen beliebiger, unverschlüsselter und unsignierter Daten an das Token. Obwohl es ratsam ist, sensible Daten im Footer zu vermeiden, kann er für kontextbezogene Informationen nützlich sein, die nicht Teil der kryptografischen Integritätsprüfung sein müssen, wie z. B. Schlüssel-IDs.
PASETO für die API-Authentifizierung in Go implementieren
Die Kernidee der Verwendung von PASETO für die API-Authentifizierung ist einfach: Wenn sich ein Benutzer erfolgreich authentifiziert (z. B. den richtigen Benutzernamen und das richtige Passwort angibt), stellt der Server einen PASETO public
-Token aus, der seine Identitätsinformationen enthält. Dieser Token wird dann an den Client zurückgesendet. Für nachfolgende API-Anfragen fügt der Client diesen PASETO in den Authorization
-Header ein. Der Server überprüft dann die Signatur des PASETO mit seinem öffentlichen Schlüssel und extrahiert die Benutzeridentität aus den Claims, um die Anfrage zu autorisieren.
Lassen Sie uns eine praktische Go-Implementierung durchgehen.
Zuerst benötigen wir eine robuste PASETO-Bibliothek für Go. Das paseto
-Paket von @o1egl ist eine beliebte und gut gewartete Wahl. Installieren Sie es:
go get github.com/o1egl/paseto
Als Nächstes betrachten wir die Struktur unseres Authentifizierungssystems. Wir benötigen Funktionen zum Generieren von Schlüsselpaaren, Ausstellen von Token und Überprüfen von Token.
1. Asymmetrische Schlüssel generieren
Für public
PASETO-Token benötigen wir ein Ed25519-Schlüsselpaar. Es ist wichtig, Ihren privaten Schlüssel sicher zu speichern und Ihren öffentlichen Schlüssel zur Überprüfung zu verteilen. Zu Demonstrationszwecken generieren wir sie im Speicher. In einer Produktionsumgebung würden diese aus sicherer Speicherung geladen werden.
package main import ( "crypto/rand" "fmt" "golang.org/x/crypto/ed25519" ) // generateKeyPair generiert ein Ed25519-Schlüsselpaar. func generateKeyPair() (ed25519.PublicKey, ed25519.PrivateKey, error) { publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, nil, fmt.Errorf("failed to generate Ed25519 key pair: %w", err) } return publicKey, privateKey, nil } // In einer realen Anwendung würden Sie diese aus Umgebungsvariablen oder einem Schlüsselverwaltungssystem laden. // Zur Demonstration behalten wir sie global bei. var ( appPrivateKey ed25519.PrivateKey appPublicKey ed25519.PublicKey ) func init() { var err error appPublicKey, appPrivateKey, err = generateKeyPair() if err != nil { panic(fmt.Sprintf("failed to initialize application keys: %v", err)) } fmt.Println("Keys generated successfully.") }
2. Ein PASETO-Token ausstellen
Wenn sich ein Benutzer anmeldet, erstellen wir ein Token. Dieses Token enthält Claims wie UserEmail
und UserID
und hat eine Gültigkeitsdauer.
package main import ( "fmt" "time" "github.com/o1egl/paseto" ) // UserClaims definiert die Struktur für unsere Token-Claims. type UserClaims struct { paseto.JSONToken UserEmail string `json:"user_email"` UserID string `json:"user_id"` } // issueToken erstellt ein neues PASETO Public Token. func issueToken(userID, userEmail string, duration time.Duration) (string, error) { // Erstellen Sie einen neuen PASETO V2 Public Builder v2 := paseto.NewV2() // Claims vorbereiten now := time.Now() exp := now.Add(duration) claims := UserClaims{ JSONToken: paseto.JSONToken{ IssuedAt: now, Expiration: exp, NotBefore: now, }, UserID: userID, UserEmail: userEmail, } // Token mit dem privaten Schlüssel signieren token, err := v2.Sign(appPrivateKey, claims, "some-optional-footer") // Footer ist optional if err != nil { return "", fmt.Errorf("failed to sign PASETO token: %w", err) } return token, nil }
3. Ein PASETO-Token überprüfen und Claims extrahieren
Bei jedem geschützten API-Aufruf empfängt der Server das Token, überprüft dessen Gültigkeit mit dem öffentlichen Schlüssel und extrahiert dann die Claims.
package main import ( "fmt" "time" "github.com/o1egl/paseto" ) // verifyToken verifiziert ein PASETO Public Token und extrahiert seine Claims. func verifyToken(token string) (*UserClaims, error) { v2 := paseto.NewV2() claims := &UserClaims{} footer := "" // Wenn Sie einen Footer verwendet hätten, würden Sie ihn hier angeben. // Token mit dem öffentlichen Schlüssel überprüfen err := v2.Verify(token, appPublicKey, claims, footer) if err != nil { return nil, fmt.Errorf("failed to verify PASETO token: %w", err) } // Die PASETO-Bibliothek prüft standardmäßig automatisch die Gültigkeit und das Nicht-Vor-Datum. // Sie können bei Bedarf zusätzliche Prüfungen hinzufügen, z. B. für benutzerdefinierte Claims. return claims, nil }
4. Integration in einen API-Endpunkt (Beispiel mit Go-net/http
)
Lassen Sie uns einen einfachen HTTP-Server aufsetzen, um den Ablauf zu demonstrieren.
package main import ( "encoding/json" "fmt" "log" "net/http" "time" ) // Login-Anforderungsrumpf type LoginRequest struct { Username string `json:"username"` Password string `json:"password"` } // Login-Antwortrumpf type LoginResponse struct { Token string `json:"token"` } // Authenticate simuliert einen Anmeldevorgang und stellt ein Token aus. func Authenticate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } // In einer echten Anwendung, Anmeldeinformationen gegen eine Datenbank validieren if req.Username != "testuser" || req.Password != "password123" { http.Error(w, "Invalid credentials", http.StatusUnauthorized) return } // PASETO-Token ausstellen token, err := issueToken("user-123", req.Username+"@example.com", 24*time.Hour) if err != nil { log.Printf("Error issuing token: %v", err) http.Error(w, "Failed to issue token", http.StatusInternalServerError) return } resp := LoginResponse{Token: token} w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) } // AuthMiddleware ist ein Middleware zum Schutz von API-Endpunkten. func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { http.Error(w, "Authorization header required", http.StatusUnauthorized) return } // Erwarte "Bearer <PASETO_TOKEN>" if len(authHeader) < 7 || authHeader[:7] != "Bearer " { http.Error(w, "Invalid Authorization header format", http.StatusUnauthorized) return } pasetoToken := authHeader[7:] claims, err := verifyToken(pasetoToken) if err != nil { log.Printf("PASETO verification failed: %v", err) http.Error(w, "Invalid or expired token", http.StatusUnauthorized) return } // Token ist gültig, Sie können jetzt claims.UserID und claims.UserEmail verwenden // um den Benutzer zu identifizieren und Autorisierungsprüfungen durchzuführen. // Zum Beispiel: Benutzerinformationen im Request-Kontext für nachfolgende Handler speichern. log.Printf("User %s (%s) authenticated successfully.", claims.UserID, claims.UserEmail) // Fahren Sie mit dem nächsten Handler fort next.ServeHTTP(w, r) } } // ProtectedEndpoint ist ein Beispiel für eine API, die eine Authentifizierung erfordert. func ProtectedEndpoint(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Welcome to the protected area!") } func main() { http.HandleFunc("/login", Authenticate) http.HandleFunc("/protected", AuthMiddleware(ProtectedEndpoint)) fmt.Println("Server listening on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }
Um dieses Beispiel auszuführen:
- Speichern Sie den Code als
main.go
. - Führen Sie
go mod init myapp
aus (falls noch nicht initialisiert). - Führen Sie
go mod tidy
aus. - Führen Sie
go run main.go
aus.
Sie können es dann mit curl
testen:
1. Login zum Abrufen eines Tokens:
curl -X POST -H "Content-Type: application/json" -d '{"username": "testuser", "password": "password123"}' http://localhost:8080/login
Dies gibt ein JSON-Objekt mit Ihrem PASETO-Token zurück. Kopieren Sie das Token.
2. Zugriff auf den geschützten Endpunkt mit dem Token:
(Ersetzen Sie <YOUR_PASETO_TOKEN>
durch das von Ihnen erhaltene Token)
curl -H "Authorization: Bearer <YOUR_PASETO_TOKEN>" http://localhost:8080/protected
Sie sollten "Welcome to the protected area!" sehen. Wenn Sie ein Token weglassen oder ein ungültiges senden, erhalten Sie eine Unauthorized
-Fehlermeldung.
Warum PASETO gegenüber JWT?
Die paseto
-Bibliothek erzwingt von Design aus mehrere Sicherheitspraktiken, die bei JWT-Implementierungen optional sind und oft übersehen werden:
- Standardmäßig sicher: PASETO definiert explizit Versionen (z. B. V1, V2, V3, V4), die jeweils an bestimmte, robuste kryptografische Algorithmen gebunden sind (z. B. V2 verwendet Ed25519 für öffentliche Schlüssel, XChacha20-Poly1305 für lokale Token). Dies eliminiert das Problem der "Algorithmenflexibilität", das häufig bei JWTs auftritt, wo unsichere Algorithmen wie
none
fälschlicherweise verwendet werden können. - Manipulationssicher: Alle PASETO-Token sind entweder kryptografisch signiert (öffentliche Token) oder verschlüsselt und signiert (lokale Token). Es gibt keine unveränderte Nutzlast, die leicht dekodiert und neu kodiert werden kann, ohne das Token ungültig zu machen.
- Einfachheit und Vorhersehbarkeit: Die PASETO-Spezifikation ist prägnanter und weniger mehrdeutig als JWT, was zu weniger Implementierungsfehlern und klareren Sicherheitsgarantien führt.
- Keine Schwachstellen bei kryptografischer Agilität: Die Versionierung von PASETO verhindert vollständig Angriffe, bei denen ein Angreifer einen Verifizierer dazu verleitet, einen schwächeren Algorithmus zu verwenden (z. B. den Wechsel von RSA zu HMAC mit dem öffentlichen Schlüssel als Geheimnis).
Anwendungsszenarien
PASETO ist eine ausgezeichnete Wahl für eine Vielzahl von API-Authentifizierungsszenarien:
- Microservices-Kommunikation: Sichere Übertragung von Benutzerkontext oder Autorisierungsdaten zwischen Diensten.
- Web-API-Authentifizierung: Der dargestellte Hauptanwendungsfall, bei dem Clients ein Token abrufen und es zur Authentifizierung nachfolgender Anfragen verwenden.
- Server-zu-Server-Authentifizierung:
local
PASETO-Token können für die sichere Kommunikation zwischen vertrauenswürdigen Diensten verwendet werden, die einen symmetrischen Schlüssel teilen. - Passwortlose Authentifizierung: PASETO-Token können als sichere, zeitlich begrenzte Login-Token dienen, die per E-Mail oder SMS gesendet werden.
Abschließende Gedanken
PASETO bietet einen erfrischenden, auf Sicherheit fokussierten Ansatz für authentifizierte Token und schlägt eine feine Balance zwischen kryptografischer Robustheit und Entwicklerfreundlichkeit. Durch die Einführung von PASETO in Ihren Go-API-Projekten können Sie die Angriffsfläche, die oft mit Token-basierter Authentifizierung verbunden ist, erheblich reduzieren und widerstandsfähigere und vertrauenswürdigere Anwendungen aufbauen. Sein zielgerichtetes Design, das Best Practices integriert, macht es zu einer attraktiven Alternative zu JWT, insbesondere für Entwickler, die "standardmäßig sichere" Prinzipien bevorzugen. Die bereitgestellten Codebeispiele veranschaulichen, dass die Implementierung von PASETO in Go unkompliziert ist und auf ausgereiften kryptografischen Primitiven und einer gut gestalteten Bibliothek beruht, um Ihre API-Kommunikation zu schützen.
Die Wahl von PASETO für Ihre Go-Anwendungen ist ein Schritt in Richtung des Aufbaus inhärent sichererer und wartungsfähigerer Systeme, der einen Token-Standard annimmt, der für moderne Sicherheitsherausforderungen entwickelt wurde.