Sichern Sie Ihre APIs mit JWT-Authentifizierung in Gin-Middleware
Min-jun Kim
Dev Intern · Leapcell

Einführung
In der modernen Webentwicklung ist die Sicherung von Programmierschnittstellen (APIs) von größter Bedeutung. Insbesondere RESTful APIs dienen oft als Rückgrat von Single-Page-Anwendungen, mobilen Apps und Microservices-Architekturen. Ein entscheidender Aspekt dieser Sicherheit ist die effektive und effiziente Authentifizierung und Autorisierung von Benutzern. Hier glänzen JSON Web Tokens (JWTs). JWTs bieten ein kompaktes, URL-sicheres Mittel zur Darstellung von Ansprüchen, die zwischen zwei Parteien übertragen werden sollen, und bieten einen zustandslosen und skalierbaren Ansatz für die Authentifizierung. Dieser Artikel befasst sich damit, wie Gin, ein weit verbreitetes Go-Web-Framework, genutzt werden kann, um die Ausstellung und Verifizierung von JWT-Token direkt in seiner Middleware zu implementieren, und bietet eine praktische und robuste Lösung zur Sicherung Ihrer Go-Anwendungen.
JWT und Gin-Middleware verstehen
Bevor wir uns mit der Implementierung befassen, klären wir die beteiligten Kernkonzepte:
- JSON Web Tokens (JWT): JWT ist ein offener Standard (RFC 7519), der eine kompakte und in sich geschlossene Methode zur sicheren Übertragung von Informationen zwischen Parteien als JSON-Objekt definiert. Diese Informationen können verifiziert und als vertrauenswürdig eingestuft werden, da sie digital signiert sind. Ein JWT besteht typischerweise aus drei Teilen: einem Header, einem Payload und einer Signatur.
- Header: Enthält den Token-Typ (JWT) und den Signierungsalgorithmus (z. B. HMAC SHA256 oder RSA).
- Payload: Enthält die Claims. Claims sind Aussagen über eine Entität (typischerweise den Benutzer) und zusätzliche Daten. Standard-Claims umfassen
iss
(Issuer),exp
(Ablaufzeit),sub
(Subject) undaud
(Audience). Benutzerdefinierte Claims können ebenfalls hinzugefügt werden. - Signatur: Wird erstellt, indem der codierte Header, der codierte Payload, ein geheimer Schlüssel und der im Header angegebene Algorithmus genommen und digital signiert werden. Diese Signatur wird verwendet, um den Absender des JWT zu verifizieren und sicherzustellen, dass die Nachricht nicht manipuliert wurde.
- Gin-Middleware: Im Kontext von Gin ist Middleware eine Kette von Funktionen, die vor oder nach einem endgültigen Anfragehandler ausgeführt werden. Middleware kann verschiedene Aufgaben ausführen, wie z. B. Protokollierung, Authentifizierung, Autorisierung, Anfrageanalyse und Fehlerbehandlung. Sie ermöglicht modularen und wiederverwendbaren Code, der Funktionalitäten zentralisiert, die für mehrere Routen gelten.
Die Implementierung von JWT in der Gin-Middleware bietet mehrere Vorteile:
- Zentralisierte Authentifizierung: Die Authentifizierungslogik wird an einem Ort gehandhabt, wodurch Wiederholungen bei mehreren Routenhandlern vermieden werden.
- Zustandslosigkeit: JWTs ermöglichen die zustandslose Authentifizierung, wodurch serverseitige Sitzungen überflüssig werden und die Skalierbarkeit verbessert wird.
- Entkoppelte Logik: Authentifizierung und Autorisierung werden von der Geschäftslogik getrennt, was den Code übersichtlicher und leichter wartbar macht.
Implementierung von JWT in der Gin-Middleware
Unsere Implementierung umfasst zwei Hauptphasen: die Ausstellung eines JWT nach erfolgreichem Benutzer-Login und die Verifizierung des JWT für nachfolgende geschützte API-Anfragen.
1. Projekt-Setup und Abhängigkeiten
Initialisieren Sie zunächst ein neues Go-Modul und installieren Sie die notwendigen Abhängigkeiten:
go mod init ihr-modulname go get github.com/gin-gonic/gin go get github.com/golang-jwt/jwt/v5
2. Definition von JWT-Claims
Wir erstellen eine benutzerdefinierte Claims
-Struktur, die jwt.RegisteredClaims
einbettet und unsere spezifischen Benutzerinformationen hinzufügt.
package main import "github.com/golang-jwt/jwt/v5" // MyCustomClaims definiert die JWT-Claims-Struktur type MyCustomClaims struct { Username string `json:"username"` jwt.RegisteredClaims }
3. JWT-Geheimer Schlüssel
Für die Signierung und Verifizierung von Token wird ein geheimer Schlüssel benötigt. In einer Produktionsumgebung sollte dies ein starker, zufällig generierter Schlüssel sein, der sicher gespeichert wird, möglicherweise als Umgebungsvariable.
// Ersetzen Sie dies in der Produktion durch einen starken, sicher gespeicherten geheimen Schlüssel var jwtSecret = []byte("supersecretkeythatshouldbesecretandlong")
4. Ausstellung eines JWT-Tokens
Diese Funktion wird nach einem erfolgreichen Benutzer-Login aufgerufen. Sie nimmt einen Benutzernamen entgegen und generiert einen signierten JWT.
package main import ( t"time" "github.com/golang-jwt/jwt/v5" ) // GenerateToken generiert ein neues JWT-Token für einen gegebenen Benutzernamen func GenerateToken(username string) (string, error) { expirationTime := time.Now().Add(24 * time.Hour) // Token ist 24 Stunden gültig claims := &MyCustomClaims{ Username: username, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationTime), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "your-app-issuer", Subject: username, ID: "", // Optional: eindeutige Kennung für das Token Audience: []string{"your-app-audience"}, }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) ttokenString, err := token.SignedString(jwtSecret) if err != nil { return "", err } return tokenString, nil }
Beispielverwendung in einer Login-Route:
package main import ( "net/http" "github.com/gin-gonic/gin" ) // LoginInput definiert die erwartete Eingabe für eine Login-Anfrage type LoginInput struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // LoginHandler verarbeitet die Benutzerauthentifizierung und Token-Ausstellung func LoginHandler(c *gin.Context) { var input LoginInput if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // In einer echten Anwendung würden Sie die Anmeldedaten mit einer Datenbank abgleichen if input.Username == "user" && input.Password == "password" { // Dummy-Anmeldedaten token, err := GenerateToken(input.Username) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler bei der Token-Generierung"}) return } c.JSON(http.StatusOK, gin.H{"token": token}) } else { c.JSON(http.StatusUnauthorized, gin.H{"error": "Ungültige Anmeldedaten"}) } }
5. JWT-Verifizierungs-Middleware
Dies ist das Herzstück unseres Authentifizierungssystems. Diese Gin-Middleware fängt Anfragen an geschützte Routen ab, extrahiert das JWT, verifiziert seine Signatur und parst seine Claims.
package main import ( "fmt" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) // AuthMiddleware ist eine Gin-Middleware zur Verifizierung von JWT-Token func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization-Header erforderlich"}) c.Abort() return } // Erwartetes Format: Bearer <token> headerParts := strings.Split(authHeader, " ") if len(headerParts) != 2 || headerParts[0] != "Bearer" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Ungültiges Format des Authorization-Headers"}) c.Abort() return } tokenString := headerParts[1] token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { // Validieren Sie die Signierungsmethode if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unerwartete Signierungsmethode: %v", token.Header["alg"]) } return jwtSecret, nil }) if err != nil { // Behandeln Sie verschiedene JWT-Fehler if ve, ok := err.(*jwt.ValidationError); ok { if ve.Errors&jwt.ValidationErrorMalformed != 0 { c.JSON(http.StatusUnauthorized, gin.H{"error": "Das ist nicht einmal ein Token"}) c.Abort() return } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { c.JSON(http.StatusUnauthorized, gin.H{"error": "Token ist entweder abgelaufen oder noch nicht aktiv"}) c.Abort() return } else { c.JSON(http.StatusUnauthorized, gin.H{"error": "Konnte dieses Token nicht verarbeiten"}) c.Abort() return } } c.JSON(http.StatusUnauthorized, gin.H{"error": "Ungültiges Token"}) c.Abort() return } if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { // Token ist gültig, speichere Claims im Context für nachfolgende Handler c.Set("username", claims.Username) c.Next() // Weiter zum nächsten Handler } else { c.JSON(http.StatusUnauthorized, gin.H{"error": "Ungültige Token Claims"}) c.Abort() } } }
6. Integration der Middleware mit Gin-Routen
Schließlich integrieren wir die AuthMiddleware
in unseren Gin-Router.
package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := gin.Default() // Öffentliche Route für Login r.POST("/login", LoginHandler) // Geschützte Routen, die JWT-Authentifizierung erfordern protected := r.Group("/api") protected.Use(AuthMiddleware()) // Wende die Authentifizierungs-Middleware an { protected.GET("/profile", func(c *gin.Context) { // Greife auf Benutzernamen aus dem Context nach erfolgreicher Authentifizierung zu username, _ := c.Get("username") c.JSON(http.StatusOK, gin.H{"message": "Willkommen zu Ihrem Profil, " + username.(string)}) }) protected.GET("/dashboard", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Dies ist Ihr Dashboard!"}) }) } r.Run(":8080") // Lausche und bediene unter 0.0.0.0:8080 }
Anwendungsfälle
Dieses JWT-Authentifizierungsmuster mit Gin-Middleware eignet sich ideal für:
- RESTful APIs: Absicherung von Endpunkten für Web- und mobile Anwendungen.
- Microservices: Authentifizierung von Anfragen zwischen verschiedenen Diensten.
- Single Page Applications (SPAs): Bereitstellung eines zustandslosen Authentifizierungsmechanismus für Frontend-Frameworks wie React, Angular oder Vue.js.
- Gateways: Für ein API Gateway kann es als erste Authentifizierungsschicht dienen, bevor Anfragen an bestimmte Dienste weitergeleitet werden.
Fazit
Die Implementierung von JWT-Ausstellung und -Verifizierung mithilfe der Gin-Middleware bietet eine leistungsstarke, skalierbare und sichere Methode zur Authentifizierung von Benutzern und zum Schutz Ihrer API-Endpunkte in Go-Anwendungen. Durch die Zentralisierung der Authentifizierungslogik und die Nutzung der in sich geschlossenen Natur von JWTs können Sie robuste und leistungsstarke Webdienste erstellen. Dieser Ansatz ermöglicht es Entwicklern letztendlich, sichere und effiziente Go-basierte API-Backends mit Vertrauen zu erstellen.