แนะนำ JWT
JWT (JSON Web Token) คือวิธีการที่กระชับและรวมทั้งลงตัวสำหรับใช้ในการยืนยันตัวตนบนเว็บไซต์ มันประกอบด้วยสามส่วน: หัวเรื่อง (header), ข้อมูลตัวเนื้อ (payload) และลายเซ็นต์ (signature) ซึ่งมีหน้าที่ในการยืนยันความสมบูรณ์และความถูกต้องของ token
JWT ใช้งานโดยส่วนใหญ่จะอยู่ที่สองรูปแบบดังนี้:
- การยืนยันตัวตน: หลังจากที่ผู้ใช้ลงชื่อเข้าใช้, คำขอที่ต่อมาจะรวม JWT เข้าไป ซึ่งช่วยให้ผู้ใช้สามารถเข้าถึงเส้นทางที่ได้รับอนุญาต, บริการและทรัพยากรต่างๆ
- การแลกเปลี่ยนข้อมูล: 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 ถ้าโทเคนถูกต้อง