Fiber JWT

Middleware JWT trả về một middleware xác thực JSON Web Token (JWT). Đối với một mã thông báo hợp lệ, nó đặt người dùng vào Ctx.Locals và gọi bộ xử lý tiếp theo. Đối với một mã thông báo không hợp lệ, nó trả về lỗi "401 - Unauthorized". Đối với một mã thông báo thiếu, nó trả về lỗi "400 - Bad Request".

Lưu ý: Yêu cầu phiên bản Go 1.19 trở lên

Cài đặt

Middleware này hỗ trợ Fiber v1 và v2, vui lòng cài đặt tương ứng.

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

Chữ ký

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

Cấu hình

Thuộc tính Kiểu Mô tả Giá trị mặc định
Filter func(*fiber.Ctx) bool Xác định một hàm để bỏ qua middleware nil
SuccessHandler func(*fiber.Ctx) error Xác định một hàm để thực thi khi có mã thông báo hợp lệ nil
ErrorHandler func(*fiber.Ctx, error) error Xác định một hàm để thực thi khi có mã thông báo không hợp lệ 401 Invalid or expired JWT
SigningKey interface{} Khóa ký được sử dụng để xác minh mã thông báo. Nếu độ dài của SigningKeys là 0, nó được sử dụng như một phương án dự phòng nil
SigningKeys map[string]interface{} Ánh xạ của các khóa ký được sử dụng để xác minh mã thông báo với trường kid nil
ContextKey string Khóa ngữ cảnh được sử dụng để lưu thông tin người dùng từ mã thông báo trong ngữ cảnh "user"
Claims jwt.Claim Dữ liệu yêu cầu mở rộng xác định nội dung của mã thông báo jwt.MapClaims{}
TokenLookup string Một chuỗi theo định dạng : được sử dụng để phân tích mã thông báo "header:Authorization"
AuthScheme string AuthScheme được sử dụng trong tiêu đề Authorization. Giá trị mặc định ("Bearer") chỉ được sử dụng với giá trị TokenLookup mặc định "Bearer"
KeyFunc func() jwt.Keyfunc Một hàm do người dùng xác định để cung cấp khóa công khai cho xác minh mã thông báo jwtKeyFunc
JWKSetURLs []string Một slice của các URL JSON Web Key (JWK) Set duy nhất được sử dụng để phân tích JWT nil

Ví dụ của 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()

	// Tuyến đường đăng nhập
	app.Post("/login", login)

	// Tuyến đường có thể truy cập mà không cần xác thực
	app.Get("/", accessible)

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

	// Tuyến đường bị hạn chế
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

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

	// Ném lỗi không được ủy quyền
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

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

	// Tạo token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Tạo mã thông báo đã mã hóa và gửi như phản hồi
	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("Accessible")
}

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

Kiểm tra HS256

Đăng nhập bằng tên người dùng và mật khẩu để nhận một mã thông báo.

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

Phản hồi

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

Sử dụng mã thông báo trong tiêu đề ủy quyền để yêu cầu tài nguyên bị hạn chế.

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

Phản hồi

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 (
	// Rõ ràng, đây chỉ là một ví dụ thử nghiệm. Đừng làm như vậy trong sản xuất.
	// Trong sản xuất, bạn nên tạo cặp khóa trước.
	// Không bao giờ thêm khóa riêng tư vào bất kỳ kho lưu trữ GitHub nào.
	privateKey *rsa.PrivateKey
)

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

	// Cho mục đích giới thiệu, tạo bộ khóa công khai / riêng tư mới mỗi khi chương trình chạy. Xem ghi chú ở trên.
	rng := rand.Reader
	var err error
	privateKey, err = rsa.GenerateKey(rng, 2048)
	if err != nil {
		log.Fatalf("rsa.GenerateKey:%v", err)
	}

	// Route đăng nhập
	app.Post("/login", login)

	// Route không xác thực
	app.Get("/", accessible)

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

	// Route bị hạn chế
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

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

	// Ném lỗi không cho phép
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	// Tạo các quyền
	claims := jwt.MapClaims{
		"name":  "John Doe",
		"admin": true,
		"exp":   time.Now().Add(time.Hour * 72).Unix(),
	}

	// Tạo mã thông báo
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

	// Tạo mã thông báo đã mã hóa và gửi nó như là phản hồi
	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("Accessible")
}

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

Kiểm tra RS256

RS256 về cơ bản cũng giống như kiểm tra HS256 ở trên.

Kiểm tra JWK Set

Những kiểm tra này cũng giống như những kiểm tra JWT cơ bản ở trên, nhưng chúng yêu cầu bộ khóa công khai hợp lệ ở định dạng JWK Set tại JWKSetURLs.

Ví dụ về KeyFunc tùy chỉnh

KeyFunc định nghĩa một hàm do người dùng xác định được sử dụng để cung cấp các khóa công khai cho việc xác thực mã thông báo. Hàm này chịu trách nhiệm xác thực thuật toán chữ ký và lựa chọn đúng khóa. Nếu mã thông báo được phát hành bởi một bên thứ ba, thì KeyFunc do người dùng xác định có thể hữu ích.

Khi có KeyFunc do người dùng xác định, SigningKey, SigningKeys và SigningMethod sẽ bị bỏ qua. Điều này là một trong ba tùy chọn để cung cấp các khóa xác thực mã thông báo. Thứ tự ưu tiên là KeyFunc do người dùng xác định, SigningKeys và SigningKey. Nếu cả SigningKeys và SigningKey đều không được cung cấp, thì hàm này phải được cung cấp. Mặc định là xác thực thuật toán chữ ký và lựa chọn khóa thích hợp bằng cách sử dụng triển khai nội bộ.

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) {
		// Kiểm tra xem phương thức chữ ký có đúng không
		if t.Method.Alg() != jwtware.HS256 {
			return nil, fmt.Errorf("Phương thức ký jwt không mong đợi=%v", t.Header["alg"])
		}

		// TODO Thực hiện tải khóa ký tùy chỉnh, ví dụ, tải từ cơ sở dữ liệu
    signingKey := "mật khẩu"

		return []byte(signingKey), nil
	}
}