Fiber JWT

Middleware JWT คืนค่า JSON Web Token (JWT) authentication middleware ในกรณีที่มี token ที่ถูกต้อง จะตั้งค่า user ใน Ctx.Locals และเรียก handler ถัดไป สำหรับ token ที่ไม่ถูกต้อง จะคืนค่า "401 - Unauthorized" error สำหรับ token ที่หายไป จะคืนค่า "400 - Bad Request" error

หมายเหตุ: จำเป็นต้องใช้ Go เวอร์ชัน 1.19 หรือสูงกว่า

การติดตั้ง

Middleware นี้รองรับ Fiber v1 และ v2 โปรดติดตั้งตามนี้

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

Signature

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

การกำหนดค่า

Property Type คำอธิบาย ค่าเริ่มต้น
Filter func(*fiber.Ctx) bool กำหนดฟังก์ชันเพื่อข้าม middleware nil
SuccessHandler func(*fiber.Ctx) error กำหนดฟังก์ชันเพื่อดำเนินการเมื่อ token ถูกต้อง nil
ErrorHandler func(*fiber.Ctx, error) error กำหนดฟังก์ชันเพื่อดำเนินการเมื่อ token ไม่ถูกต้อง 401 Invalid or expired JWT
SigningKey interface{} คีย์ที่ใช้ในการยืนยัน token หากความยาวของ SigningKeys เป็น 0 จะใช้เป็น fallback nil
SigningKeys map[string]interface{} การแมปคีย์ของ คีย์ที่ใช้ในการยืนยัน token พร้อมกับฟิลด์ kid nil
ContextKey string คีย์ของ context ที่ใช้เก็บข้อมูลผู้ใช้จาก token ใน context "user"
Claims jwt.Claim ข้อมูลเพิ่มเติมของ claims ที่กำหนดรูปแบบของ token jwt.MapClaims{}
TokenLookup string สตริงในรูปแบบ : ที่ใช้ในการแยก token "header:Authorization"
AuthScheme string AuthScheme ที่ใช้ในหัวข้อ Authorization header ค่าเริ่มต้น ("Bearer") จะใช้กับค่าเริ่มต้นของ TokenLookup เท่านั้น "Bearer"
KeyFunc func() jwt.Keyfunc ฟังก์ชันที่กำหนดโดยผู้ใช้เพื่อให้คีย์สาธารณะสำหรับการยืนยัน token jwtKeyFunc
JWKSetURLs []string ลิสต์ของ unique JSON Web Key (JWK) Set URLs ที่ใช้ในการแยก JWT nil

ตัวอย่างของ 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()

	// เส้นทาง Login
	app.Post("/login", login)

	// เส้นทางที่เข้าถึงได้โดยไม่ต้องการการพิสูจน์ตัวตน
	app.Get("/", accessible)

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

	// เส้นทางที่จำกัดสิทธิ์
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

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

	// ลักษณะของคำผิดอำนาจ
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	// สร้าง claims
	claims := jwt.MapClaims{
		"name":  "John Doe",
		"admin": true,
		"exp":   time.Now().Add(time.Hour * 72).Unix(),
	}

	// สร้าง token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// สร้าง token ที่เข้ารหัสและส่งเป็นตอบกลับ
	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)
}

ทดสอบ HS256

เข้าสู่ระบบด้วยชื่อผู้ใช้และรหัสผ่านเพื่อรับโทเค็น

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

การตอบรับ

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

ขอข้อมูลจำกัดโดยใช้โทเค็นในส่วนหัวการอนุญาต

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

การตอบรับ

ยินดีต้อนรับ 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 (
	// อย่างแน่นอนว่านี้เป็นตัวอย่างการทดสอบเท่านั้น อย่าทำเช่นนี้ในการใช้งานจริง
	// ในการใช้งานจริงคุณควรสร้างคู่กุญแจล่วงหน้า
	// อย่าเพิ่มกุญแจส่วนตัวลงในที่เก็บข้อมูลของ GitHub
	privateKey *rsa.PrivateKey
)

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

	// เพื่อการสาธิต สร้างคู่กุญแจส่วนตัว/สาธิตใหม่ทุกครั้งที่โปรแกรมทำงาน ดูหมายเหตุข้างต้น
	rng := rand.Reader
	var err error
	privateKey, err = rsa.GenerateKey(rng, 2048)
	if err != nil {
		log.Fatalf("rsa.GenerateKey:%v", err)
	}

	// เส้นทางล็อกอิน
	app.Post("/login", login)

	// เส้นทางที่ไม่ได้รับการรับรอง
	app.Get("/", accessible)

	// มีการใช้ JWT middleware 
	app.Use(jwtware.New(jwtware.Config{
		SigningKey: jwtware.SigningKey{
			JWTAlg: jwtware.RS256,
			Key:    privateKey.Public(),
		},
	}))

	// เส้นทางที่ถูกจำกัด
	app.Get("/restricted", restricted)

	app.Listen(":3000")
}

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

	// โยนข้อผิดพลาดการไม่อนุญาต
	if user != "john" || pass != "doe" {
		return c.SendStatus(fiber.StatusUnauthorized)
	}

	// สร้างข้อตั้ง
	claims := jwt.MapClaims{
		"name":  "John Doe",
		"admin": true,
		"exp":   time.Now().Add(time.Hour * 72).Unix(),
	}

	// สร้างโทเค็น
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)

	// สร้างโทเค็นที่เข้ารหัสแล้วและส่งมันเป็นการตอบรับ
	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("สามารถเข้าถึงได้")
}

func restricted(c *fiber.Ctx) error {
	user := c.Locals("user").(*jwt.Token)
	claims := user.Claims.(jwt.MapClaims)
	name := claims["name"].(string)
	return c.SendString("ยินดีต้อนรับ " + name)
}

ทดสอบ RS256

RS256 ก็คืออย่างเดียวกับทดสอบ HS256 ข้างต้น

ทดสอบ JWK Set

ทดสอบเหล่านี้เหมือนกับทดสอบ JWT พื้นฐานข้างต้น แต่ต้องการเซตกุญแจสาธิตที่ถูกต้องในรูปแบบ JWK Set ที่ JWKSetURLs

ตัวอย่าง KeyFunc ที่กำหนดเอง

KeyFunc กำหนดฟังก์ชั่นที่กำหนดโดยผู้ใช้เพื่อให้คีย์สาธารณะสำหรับการตรวจสอบโทเค็น ฟังก์ชั่นนี้รับผิดชอบในการตรวจสอบวิธีการลงลายและเลือกคีย์ที่ถูกต้อง หากโทเค็นถูกออกโดยฝ่ายภายนอก การกำหนด KeyFunc ที่ผู้ใช้กำหนดอาจจะมีประโยชน์

เมื่อกำหนด KeyFunc ที่ผู้ใช้กำหนดไว้ ค่า SigningKey, SigningKeys, และ SigningMethod จะถูกละเว้น นี่เป็นหนึ่งในทางเลือกสามตัวสำหรับการกำหนดคีย์ตรวจสอบโทเค็น ลำดับความสำคัญคือ KeyFunc ที่ผู้ใช้กำหนด, SigningKeys, และ SigningKey หากไม่ได้กำหนด SigningKeys หรือ SigningKey ฟังก์ชั่นนี้ต้องได้รับการกำหนดไว้ ค่าเริ่มต้นคือการตรวจสอบวิธีการลงลายและเลือกคีย์ที่เหมาะสมโดยการดำเนินการภายใน

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) {
		// ตรวจสอบว่าวิธีการลงลายถูกต้องหรือไม่
		if t.Method.Alg() != jwtware.HS256 {
			return nil, fmt.Errorf("วิธีการลงลายของ jwt ไม่คาดคะเน=%v", t.Header["alg"])
		}

		// ทำการดำเนินการโหลดคีย์ตรวจสอบที่กำหนดเอง เช่น โหลดจากฐานข้อมูล
    signingKey := "secret"

		return []byte(signingKey), nil
	}
}