Fiber JWT

Il middleware JWT restituisce un middleware di autenticazione JSON Web Token (JWT). Per un token valido, imposta l'utente in Ctx.Locals e chiama il gestore successivo. Per un token non valido, restituisce un errore "401 - Non autorizzato". Per un token mancante, restituisce un errore "400 - Richiesta non valida".

Nota: È richiesta la versione Go 1.19 o successiva

Installazione

Questo middleware supporta Fiber v1 e v2, installare di conseguenza.

go get -u github.com/gofiber/fiber/v2
go get -u github.com/gofiber/contrib/jwt
go get -u github.com/golang-jwt/jwt/v5

Firma

jwtware.New(config ...jwtware.Config) func(*fiber.Ctx) error

Configurazione

Proprietà Tipo Descrizione Valore predefinito
Filter func(*fiber.Ctx) bool Definisce una funzione per saltare il middleware nil
SuccessHandler func(*fiber.Ctx) error Definisce una funzione da eseguire al token valido nil
ErrorHandler func(*fiber.Ctx, error) error Definisce una funzione da eseguire al token non valido 401 JWT non valido o scaduto
SigningKey interface{} La chiave di firma utilizzata per verificare il token. Se la lunghezza di SigningKeys è 0, viene utilizzata come fallback nil
SigningKeys map[string]interface{} Mappatura delle chiavi di firma utilizzate per verificare i token con un campo kid nil
ContextKey string La chiave di contesto utilizzata per memorizzare le informazioni dell'utente dal token nel contesto "user"
Claims jwt.Claim Dati estesi delle rivendicazioni che definiscono il contenuto del token jwt.MapClaims{}
TokenLookup string Una stringa nel formato : utilizzata per analizzare il token "header:Authorization"
AuthScheme string L'AuthScheme utilizzato nell'intestazione di autorizzazione. Il valore predefinito ("Bearer") è utilizzato solo con il valore predefinito TokenLookup "Bearer"
KeyFunc func() jwt.Keyfunc Una funzione definita dall'utente per fornire la chiave pubblica per la verifica del token jwtKeyFunc
JWKSetURLs []string Una sequenza di URL univoci del JSON Web Key (JWK) Set utilizzati per analizzare JWT nil

Esempio di HS256

package main

import (
	"time"

	"github.com/gofiber/fiber/v2"

	jwtware "github.com/gofiber/contrib/jwt"
	"github.com/golang-jwt/jwt/v5"
)

func main() {
	app := fiber.New()

	// Percorso di accesso
	app.Post("/login", login)

	// Percorso accessibile senza autenticazione
	app.Get("/", accessible)

	// Middleware JWT
	app.Use(jwtware.New(jwtware.Config{
		SigningKey: jwtware.SigningKey{Key: []byte("segreto")},
	}))

	// Percorso limitato
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

func login(c *fiber.Ctx) error {
	user := c.FormValue("user")
	pass := c.FormValue("pass")

	// Lancio di un errore di autorizzazione
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	// Creazione delle rivendicazioni
	claims := jwt.MapClaims{
		"name":  "John Doe",
		"admin": true,
		"exp":   time.Now().Add(time.Hour * 72).Unix(),
	}

	// Creazione del token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Generazione del token codificato e invio come risposta
	t, err := token.SignedString([]byte("segreto"))
	if err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(fiber.Map{"token": t})
}

func accessible(c *fiber.Ctx) error {
	return c.SendString("Accessibile")
}

func restricted(c *fiber.Ctx) error {
	user := c.Locals("user").(*jwt.Token)
	claims := user.Claims.(jwt.MapClaims)
	name := claims["name"].(string)
	return c.SendString("Benvenuto " + name)
}

Test HS256

Accedi con nome utente e password per ottenere un token.

curl --data "user=john&pass=doe" http://localhost:3000/login

Risposta

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"
}

Richiesta per accedere a una risorsa limitata utilizzando il token nell'intestazione di autorizzazione.

curl localhost:3000/restricted -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NjE5NTcxMzZ9.RB3arc4-OyzASAaUhC2W3ReWaXAt_z2Fd3BN4aWTgEY"

Risposta

Welcome John Doe
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"log"
	"time"

	"github.com/gofiber/fiber/v2"

	"github.com/golang-jwt/jwt/v5"

	jwtware "github.com/gofiber/contrib/jwt"
)

var (
	// Ovviamente, questo è solo un esempio di test. Non farlo in produzione.
	// In produzione, è necessario generare preventivamente la coppia di chiavi.
	// Non aggiungere mai la chiave privata a un repository GitHub.
	privateKey *rsa.PrivateKey
)

func main() {
	app := fiber.New()

	// Per scopi dimostrativi, genera una nuova coppia di chiavi privata/pubblica ogni volta che il programma viene eseguito. Vedi la nota sopra.
	rng := rand.Reader
	var err error
	privateKey, err = rsa.GenerateKey(rng, 2048)
	if err != nil {
		log.Fatalf("rsa.GenerateKey:%v", err)
	}

	// Percorso di accesso
	app.Post("/login", login)

	// Percorso non autenticato
	app.Get("/", accessibile)

	// Middleware JWT
	app.Use(jwtware.New(jwtware.Config{
		SigningKey: jwtware.SigningKey{
			JWTAlg: jwtware.RS256,
			Key:    privateKey.Public(),
		},
	}))

	// Percorso limitato
	app.Get("/restricted", limitato)

	app.Listen(":3000")
}

func login(c *fiber.Ctx) error {
	user := c.FormValue("user")
	pass := c.FormValue("pass")

	// Genera errore non autorizzato
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	// Crea affermazioni
	claims := jwt.MapClaims{
		"name":  "John Doe",
		"admin": true,
		"exp":   time.Now().Add(time.Hour * 72).Unix(),
	}

	// Crea token
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

	// Genera token codificato e invialo come risposta
	t, err := token.SignedString(privateKey)
	if err != nil {
		log.Printf("token.SignedString:%v", err)
		return c.SendStatus(fiber.StatusInternalServerError)
	}

	return c.JSON(fiber.Map{"token": t})
}

func accessibile(c *fiber.Ctx) error {
	return c.SendString("Accessibile")
}

func limitato(c *fiber.Ctx) error {
	user := c.Locals("user").(*jwt.Token)
	claims := user.Claims.(jwt.MapClaims)
	name := claims["name"].(string)
	return c.SendString("Welcome " + name)
}

Test RS256

RS256 è essenzialmente lo stesso del test HS256 sopra.

Test JWK Set

Questi test sono gli stessi dei test JWT di base sopra, ma richiedono set di chiavi pubbliche valide nel formato JWK Set a JWKSetURLs.

Esempio di KeyFunc personalizzata

La KeyFunc definisce una funzione definita dall'utente utilizzata per fornire chiavi pubbliche per la convalida del token. Questa funzione è responsabile della convalida dell'algoritmo di firma e della selezione della chiave corretta. Se il token è emesso da una parte esterna, la KeyFunc definita dall'utente può essere utile.

Quando viene fornita una KeyFunc definita dall'utente, le SigningKey, SigningKeys e SigningMethod verranno ignorati. Questa è una delle tre opzioni per fornire chiavi di convalida del token. L'ordine di priorità è la KeyFunc definita dall'utente, SigningKeys e SigningKey. Se né SigningKeys né SigningKey vengono forniti, questa funzione deve essere fornita. L'impostazione predefinita è convalidare l'algoritmo di firma e selezionare la chiave appropriata utilizzando l'implementazione interna.

package main

import (
	"fmt"
	"github.com/gofiber/fiber/v2"
	
  jwtware "github.com/gofiber/contrib/jwt"
  "github.com/golang-jwt/jwt/v5"
)

func main() {
	app := fiber.New()

	app.Use(jwtware.New(jwtware.Config{
		KeyFunc: customKeyFunc(),
	}))

	app.Get("/ok", func(c *fiber.Ctx) error {
		return c.SendString("OK")
	})
}

func customKeyFunc() jwt.Keyfunc {
	return func(t *jwt.Token) (interface{}, error) {
		// Controlla se il metodo di firma è corretto
		if t.Method.Alg() != jwtware.HS256 {
			return nil, fmt.Errorf("Metodo di firma jwt inaspettato=%v", t.Header["alg"])
		}

		// TODO Implementare il caricamento personalizzato della chiave di firma, ad esempio, caricare dal database
    signingKey := "segreto"

		return []byte(signingKey), nil
	}
}