Fiber JWT

Middleware JWT zwraca middleware uwierzytelniające JSON Web Token (JWT). Dla poprawnego tokena ustawia użytkownika w Ctx.Locals i wywołuje następny handler. Dla nieprawidłowego tokenu zwraca błąd "401 - Nieautoryzowane". W przypadku braku tokenu zwraca błąd "400 - Złe żądanie".

Uwaga: Wymagana jest wersja Go 1.19 lub nowsza

Instalacja

To middleware obsługuje Fiber v1 i v2, zainstaluj odpowiednio.

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

Sygnatura

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

Konfiguracja

Właściwość Typ Opis Domyślna wartość
Filter func(*fiber.Ctx) bool Definiuje funkcję pomijającą middleware nil
SuccessHandler func(*fiber.Ctx) error Definiuje funkcję do wykonania po prawidłowym tokenie nil
ErrorHandler func(*fiber.Ctx, error) error Definiuje funkcję do wykonania po nieprawidłowym tokenie 401 Nieprawidłowy lub wygasły JWT
SigningKey interface{} Klucz podpisujący używany do weryfikacji tokenu. Jeśli długość SigningKeys wynosi 0, jest używany jako zapasowy nil
SigningKeys map[string]interface{} Odwzorowanie kluczy podpisujących używanych do weryfikacji tokenów z polem kid nil
ContextKey string Klucz kontekstu używany do przechowywania informacji o użytkowniku z tokenu w kontekście "user"
Claims jwt.Claim Rozszerzone dane twierdzeń definiujące treść tokenu jwt.MapClaims{}
TokenLookup string Ciąg w formacie : używany do analizy tokenu "header:Authorization"
AuthScheme string Schemat AuthScheme używany w nagłówku Autoryzacji. Domyślna wartość ("Bearer") jest używana tylko z domyślną wartością TokenLookup "Bearer"
KeyFunc func() jwt.Keyfunc Funkcja zdefiniowana przez użytkownika do dostarczania klucza publicznego do weryfikacji tokenu jwtKeyFunc
JWKSetURLs []string Tablica unikalnych adresów URL zestawu kluczy JSON Web (JWK) używanych do analizy JWT nil

Przykład 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()

	// Trasa logowania
	app.Post("/login", login)

	// Trasa dostępna bez uwierzytelniania
	app.Get("/", accessible)

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

	// Ograniczona trasa
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

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

	// Wyrzucanie błędu nieautoryzacji
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

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

	// Tworzenie tokenu
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Generowanie zakodowanego tokenu i wysyłanie jako odpowiedź
	t, err := token.SignedString([]byte("secret"))
	if err != nil {
		return c.SendStatus(fiber.StatusInternalServerError)
	}

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

func accessible(c *fiber.Ctx) error {
	return c.SendString("Dostępne")
}

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

Test HS256

Zaloguj się za pomocą nazwy użytkownika i hasła, aby uzyskać token.

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

Odpowiedź

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

Ogranicz dostęp do zasobu za pomocą tokenu w nagłówku autoryzacyjnym.

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

Odpowiedź

Witaj 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 (
	// Oczywiście, jest to tylko przykładowy test. Nie rób tak w produkcji.
	// W produkcji klucz prywatny należy wygenerować z wyprzedzeniem.
	// Nigdy nie dodawaj klucza prywatnego do żadnego repozytorium GitHub.
	privateKey *rsa.PrivateKey
)

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

	// Dla celów demonstracyjnych generuj nową parę kluczy publiczny/prywatny za każdym razem, gdy program się uruchamia. Patrz notatka powyżej.
	rng := rand.Reader
	var err error
	privateKey, err = rsa.GenerateKey(rng, 2048)
	if err != nil {
		log.Fatalf("rsa.GenerateKey:%v", err)
	}

	// Trasa logowania
	app.Post("/login", login)

	// Trasa bez uwierzytelniania
	app.Get("/", accessible)

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

	// Ograniczona trasa
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

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

	// Wyrzuć błąd braku autoryzacji
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

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

	// Utwórz token
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

	// Wygeneruj zakodowany token i wyślij go jako odpowiedź
	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 accessible(c *fiber.Ctx) error {
	return c.SendString("Dostępny")
}

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

Test RS256

RS256 jest zasadniczo taki sam jak powyższy test HS256.

Test zestawu JWK

Te testy są takie same jak testy podstawowych JWT powyżej, jednak wymagają prawidłowych zestawów kluczy publicznych w formacie JWK Set pod JWKSetURLs.

Przykład niestandardowej funkcji KeyFunc

KeyFunc definiuje funkcję zdefiniowaną przez użytkownika, która służy do dostarczania kluczy publicznych do weryfikacji tokenów. Ta funkcja jest odpowiedzialna za weryfikację algorytmu sygnatury oraz wybór poprawnego klucza. Jeśli token jest wydany przez zewnętrzną stronę, niestandardowa funkcja KeyFunc może być przydatna.

Gdy została dostarczona niestandardowa funkcja KeyFunc, parametry SigningKey, SigningKeys oraz SigningMethod będą ignorowane. Jest to jedna z trzech opcji dostarczenia kluczy weryfikacyjnych tokenów. Kolejność priorytetów to: niestandardowa funkcja KeyFunc, SigningKeys oraz SigningKey. Jeśli żaden z parametrów SigningKeys ani SigningKey nie został dostarczony, ta funkcja musi zostać wskazana. Domyślnie dokonywana jest weryfikacja algorytmu sygnatury oraz wybór odpowiedniego klucza przy użyciu implementacji wewnętrznej.

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) {
		// Sprawdź, czy metoda sygnatury jest poprawna
		if t.Method.Alg() != jwtware.HS256 {
			return nil, fmt.Errorf("Nieoczekiwana metoda sygnatury jwt=%v", t.Header["alg"])
		}

		// TODO Własna implementacja wczytywania klucza sygnatury, np. wczytaj z bazy danych
    signingKey := "sekret"

		return []byte(signingKey), nil
	}
}