แนะนำ JWT

JWT (JSON Web Token) คือวิธีการที่กระชับและรวมทั้งลงตัวสำหรับใช้ในการยืนยันตัวตนบนเว็บไซต์ มันประกอบด้วยสามส่วน: หัวเรื่อง (header), ข้อมูลตัวเนื้อ (payload) และลายเซ็นต์ (signature) ซึ่งมีหน้าที่ในการยืนยันความสมบูรณ์และความถูกต้องของ token

JWT ใช้งานโดยส่วนใหญ่จะอยู่ที่สองรูปแบบดังนี้:

  1. การยืนยันตัวตน: หลังจากที่ผู้ใช้ลงชื่อเข้าใช้, คำขอที่ต่อมาจะรวม JWT เข้าไป ซึ่งช่วยให้ผู้ใช้สามารถเข้าถึงเส้นทางที่ได้รับอนุญาต, บริการและทรัพยากรต่างๆ
  2. การแลกเปลี่ยนข้อมูล: JWT เป็นวิธีการที่ดีสำหรับการส่งข้อมูลไปมาอย่างปลอดภัยระหว่างฝ่ายต่างๆ เพราะมันสามารถถูกลงชื่อด้วยเป็นดิจิทัล ตัวอย่างเช่นการใช้คู่กุญแจสาธารณะ/ส่วนตัว

รูปแบบข้อมูล JWT

JWT เป็นวิธีที่กระชับและปลอดภัยสำหรับแทนข้อมูลที่ต้องการแลกเปลี่ยนระหว่างฝ่ายต่างๆ ในความเป็นจริง JWT ประกอบด้วยสามส่วน, ถูกแยกออกด้วยจุด (.) นั่นคือ หัวเรื่อง.ข้อมูลตัวเนื้อ.ลายเซ็นต์ ต่อไปนี้เราจะอธิบายรายละเอียดของสามส่วนดังกล่าว

1. หัวเรื่อง

หัวเรื่องมักประกอบด้วยสองส่วน: ประเภทของ token - ที่ปกติจะเป็น JWT, และขั้นวิธีการลงชื่อหรือเข้ารหัสที่ใช้ เช่น HMAC SHA256 หรือ RSA หัวเรื่องถูกแสดงในรูปแบบ JSON และจากนั้นถูกเข้ารหัสเป็นสตริงโดยใช้ Base64Url ตัวอย่างเช่น:

{
  "alg": "HS256",
  "typ": "JWT"
}

หลังจากที่เข้ารหัสแล้ว, คุณจะได้รับสตริงที่คล้ายกับนี้:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. ข้อมูลตัวเนื้อ

ข้อมูลตัวเนื้อประกอบด้วยชุดของข้อมูล (claims) ซึ่งเป็นข้อมูลเกี่ยวกับประเภทต่างๆ (แบบปกติจะเป็นการระบุตัวตนของผู้ใช้) และข้อมูลอื่นๆ ข้อมูลตัวเนื้อสามารถรวมพร้อมกันด้วยข้อมูลที่กำหนดไว้ล่วงหน้า (ที่รู้จักกันนี้หากใช้หรือ Custom Claims) หรือข้อมูลที่กำหนดเอง

การกำหนดไว้ล่วงหน้าสามารถรวมเช่น:

  • iss (Issuer): ผู้ออก
  • exp (Expiration Time): เวลาหมดอายุ
  • sub (Subject): เรื่อง
  • aud (Audience): กลุ่มเป้าหมาย
  • iat (Issued At): เวลาออก
  • nbf (Not Before): เวลาเป็นปัจจุบัน
  • jti (JWT ID): ตัวแทนเฉพาะของ JWT

ตัวอย่างข้อมูลตัวเนื้ออาจจะมีลุ้นด้วยนี้ (ในรูปแบบ JSON):

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

ข้อมูลนี้จะถูกเข้ารหัสด้วย Base64Url และคุณจะได้รับสตริงที่คล้ายกับนี้:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

3. ลายเซ็นต์

ส่วนลายเซ็นต์ถูกใช้ในการลงลายเซ็นต์สองสตริงที่เข้ารหัสที่กล่าวถึงข้างต้น, เพื่อทำการตรวจสอบว่าข้อความนั้นไม่ได้ถูกรบกวนระหว่างการส่งข้อมูล ก่อนที่จะต้องระบุคีย์ (หากใช้วิธีการลงลายเซ็นต์ HMAC SHA256) และจากนั้นใช้ขั้นวิธีที่ได้รับมาในหัวเรื่องเพื่อลงพร้อมกับหัวเรื่องและข้อมูลตัวเนือ

ตัวอย่างเช่น, หากคุณมีหัวเรื่องและข้อมูลตัวเนื้อตามนี้:

HeaderEncoded.HeaderPayload

Pseudocode สำหรับการลงลายเซ็นต์โดยใช้คีย์จะอาจจะดังนี้:

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey)

สตริงที่ลงลายเซ็นต์ที่ได้อาจเป็นดังนี้:

dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY

JWT สมบูรณ์

การรวมสามส่วนนี้ด้วยจุด (.) ทำให้ได้ JWT สมบูรณ์:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY

มาตรฐาน JWT ยืดหยุ่นมากและสามารถเก็บข้อมูลใดๆ ก็ได้ที่คุณต้องการในข้อมูลตัวเนื้อ (แต่เพื่อลดขนาดของ JWT และเพื่อเหตุผลด้านความปลอดภัย, ข้อมูลที่เป็นที่ละเมิดไม่ควรถูกเก็บไว้) และยังรักษาความสมบูรณ์ของข้อมูลนี้ผ่านทางลายเซ็นต์ด้วย

การติดตั้งไลบรารี JWT ของ Go

ชุมชน Go มีไลบรารีชื่อ github.com/golang-jwt/jwt สำหรับการจัดการ JWT ให้ใช้งาน การติดตั้งไลบรารีนี้ง่ายมากเพียงแค่รันคำสั่งต่อไปนี้ในไดเรกทอรีโปรเจคของคุณ:

go get -u github.com/golang-jwt/jwt/v5

เมื่อติดตั้งแล้ว คุณสามารถเรียกใช้ได้ดังนี้:

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

สร้างโทเคนง่าย ๆ

เพื่อสร้างโทเคน JWT ง่าย ๆ โดยใช้ภาษา Go และอัลกอริทึม HS256 คุณต้องทำตามขั้นตอนนี้:

เริ่มต้นด้วยการสร้างอ็อบเจกต์ Token ใหม่ โดยใช้อัลกอริทึม HS256:

token := jwt.New(jwt.SigningMethodHS256)

ต่อมา ใช้เมธอด SignedString เพื่อสร้างการแสดงผลเป็นสตริงของโทเคนนี้โดยส่งกุญแจที่คุณจะใช้สำหรับการเซ็นต์:

var mySigningKey = []byte("your-256-bit-secret")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
    log.Fatalf("เกิดข้อผิดพลาด: %v", err)
}
fmt.Println(strToken)

สิ่งนี้จะสร้างโทเคนง่ายๆ โดยไม่มีการเรียกร้องใดๆ

สร้างโทเคนพร้อมพารามิเตอร์

หนึ่งในฟังก์ชันหลักของ JWT คือการถือข้อมูล ข้อมูลนี้ถูกเข้ารหัสในโทเคนผ่านทาง claims ตัวอย่างเช่น ลองสร้าง claims ที่กำหนดเอง:

// โครงสร้างของ Claims ที่กำหนดเอง
type MyClaims struct {
	jwt.RegisteredClaims
	Username string `json:"username"`
	Admin    bool   `json:"admin"`
}

// สร้างโทเคนพร้อม claims ที่กำหนดเอง
claims := MyClaims{
    RegisteredClaims: jwt.RegisteredClaims{},
    Username: "my_username",
	Admin: true,
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

// กุญแจลับสำหรับการเซ็นต์
var mySigningKey = []byte("your-256-bit-secret")

// สร้างโทเคนในรูปแบบสตริง
strToken, err := token.SignedString(mySigningKey)
if err != nil {
    log.Fatalf("เกิดข้อผิดพลาด: %v", err)
}

fmt.Println(strToken)

ในโค้ดข้างต้นเราได้กำหนดโครงสร้าง MyClaims เพื่อมีข้อมูลที่กำหนดเองรวมถึงข้อมูลที่ลงทะเบียนไว้ จากนั้น เรายังคงใช้ SignedString เพื่อสร้างสตริงโทเคน

แยกวิเคราะห์และตรวจสอบโทเคน

การแยกวิเคราะห์และตรวจสอบ JWT สำคัญมาก และคุณสามารถทำได้ตามนี้:

// เราจะใช้โครงสร้าง MyClaims เหมือนเดิม
tokenString := "your-JWT-string"

// เราต้องกำหนดฟังก์ชันที่แพ็คเกจ jwt จะใช้ในการแยกวิเคราะห์ tokenString
keyFunc := func(t *jwt.Token) (interface{}, error) {
	// ตรวจสอบว่าใช้วิธีการเซ็นต์ที่คาดหวังหรือไม่
	if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
	    return nil, fmt.Errorf("วิธีการเซ็นต์ไม่คาดหวัง: %v", t.Header["alg"])
	}
	// ส่งกลับกุญแจลับสำหรับโทเคน JWT ในรูปแบบ []byte ที่เข้ากันกับกุญแจที่ใช้สำหรับการเซ็นต์ก่อนหน้า
	return mySigningKey, nil
}

// แยกวิเคราะห์โทเคน
claims := &MyClaims{}
parsedToken, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
	log.Fatalf("ข้อผิดพลาดในการแยกวิเคราะห์: %v", err)
}

if !parsedToken.Valid {
    log.Fatalf("โทเคนไม่ถูกต้อง")
}

// ณ ขั้นตอนนี้ parsedToken ได้รับการตรวจสอบแล้ว และเราสามารถอ่าน claims
fmt.Printf("ผู้ใช้: %s, ผู้ดูแลระบบ: %v\n", claims.Username, claims.Admin)

ในโค้ดข้างต้น เราให้สตริงโทเคน MyClaims ตัวอย่าง และฟังก์ชัน keyFunc กับ jwt.ParseWithClaims ฟังก์ชัน ฟังก์ชันนี้จะตรวจสอบลายเซ็นและแยกวิเคราะห์โทเคน และเติม claims ถ้าโทเคนถูกต้อง