مقدمة إلى JWT
JWT (JSON Web Token) هو أسلوب موجز ومكتمل ذاتياً للمصادقة على الويب. يتكون من ثلاثة أجزاء: الرأس (Header) والحمولة (Payload) والتوقيع (Signature). يحتوي الرأس على نوع الرمز (Token) ومعلومات خوارزمية التشفير، تحتوي الحمولة على البيانات الواجب نقلها (عادةً تتضمن بعض معلومات الإذن وتحديد الهوية للمستخدم)، ويتم استخدام التوقيع للتحقق من سلامة وصحة الرمز.
يتم استخدام JWT بشكل رئيسي في حالتين:
- المصادقة: بمجرد تسجيل مستخدم الدخول، سيتم إرفاق كل طلب لاحق برمز JWT، الأمر الذي يسمح للمستخدم بالوصول إلى المسارات، الخدمات والموارد المسموح بها.
- تبادل المعلومات: يعتبر JWT طريقة جيدة لنقل المعلومات بشكل آمن بين الأطراف، يمكن توقيعها رقمياً مثلاً باستخدام أزواج المفاتيح العمومية/الخاصة.
تنسيق بيانات JWT
JWT هو طريقة مدمجة وآمنة لتمثيل المعلومات المراد تبادلها بين الأطراف. يتكون JWT من ثلاثة أجزاء، مفصولة بنقط (.``)، وهي
الرأس (Header).الحمولة (Payload).التوقيع (Signature)`. فيما يلي سنوضح تنسيق بيانات هذه الأجزاء الثلاثة.
1. الرأس (Header)
يتكون الرأس عادةً من قسمين: نوع الرمز (عادةً JWT
) وخوارزمية التوقيع أو التشفير المستخدمة، مثل HMAC SHA256
أو RSA
. يتم تمثيل الرأس في شكل JSON ثم ترميزه إلى سلسلة باستخدام Base64Url. على سبيل المثال:
{
"alg": "HS256",
"typ": "JWT"
}
بعد الرمز، قد تحصل على سلسلة مشابهة لهذه:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. الحمولة (Payload)
تتكون الحمولة من مجموعة من الادعاءات، وهي معلومات حول الكيان (عادةً مستخدم) وبيانات أخرى. قد تشمل الحمولة العديد من الادعاءات المحددة مسبقاً (المعروفة أيضاً بالادعاءات المسجلة) والادعاءات الخاصة (المطالبات الخاصة).
يمكن أن تتضمن الادعاءات المحددة مسبقاً مثل:
-
iss
(المرسل): المرسل -
exp
(وقت الانتهاء): وقت الانتهاء -
sub
(الموضوع): الموضوع -
aud
(المستمع): المستمع -
iat
(الوقت المصدر): الوقت الذي تم فيه الإصدار -
nbf
(غير قبل): الوقت الفعال -
jti
(هوية JWT): هوية فريدة للرمز JWT
قد تبدو مثالاً للحمولة مثل هذا (بتنسيق JSON):
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
تتم ترميز هذه المعلومات أيضاً باستخدام Base64Url، وقد تحصل على سلسلة مشابهة لهذه:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
3. التوقيع (Signature)
تستخدم قسم التوقيع لتوقيع السلسلتين المرمزتين المذكورتين أعلاه، للتحقق من أن الرسالة لم يتم التلاعب بها أثناء النقل. أولاً، تحتاج إلى تحديد مفتاح (في حالة استخدام خوارزمية HMAC SHA256) ثم استخدام الخوارزمية المحددة في الرأس لتوقيع الرأس والحمولة.
على سبيل المثال، إذا كان لديك الرأس والحمولة التاليين:
الرأس المرمز.الحمولة المرمزة
الشفرة الوهمية لتوقيعهم باستخدام مفتاح قد تكون كالتالي:
HMACSHA256(base64UrlEncode(الرأس) + "." + base64UrlEncode(الحمولة), مفتاح سري)
قد تكون السلسلة الموقعة الناتجة مثلاً:
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
كما يلي:
import "github.com/golang-jwt/jwt/v5"
إنشاء رمز مميز بسيط
لإنشاء رمز JWT بسيط باستخدام لغة Go وخوارزمية HS256، عليك اتباع الخطوات التالية:
أولًا، قم بإنشاء كائن Token جديد باستخدام خوارزمية HS256:
token := jwt.New(jwt.SigningMethodHS256)
ثم، استخدم طريقة SignedString
لإنشاء تمثيل نصي لهذا الرمز، مررًا المفتاح الذي ستستخدمه للتوقيع.
var mySigningKey = []byte("السر_الخاص_بك_٢٥٦_بت")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("حدث خطأ: %v", err)
}
fmt.Println(strToken)
سيؤدي هذا إلى إنشاء رمز بسيط بدون أي سجلات.
إنشاء رمز مميز بمعلمات
إحدى الوظائف الرئيسية لـ JWT هي حمل المعلومات. يتم ترميز هذه المعلومات في الرمز من خلال المطالب. على سبيل المثال، دعنا ننشئ مطالب مخصصة:
// هيكل المطالب المخصصة
type MyClaims struct {
jwt.RegisteredClaims
Username string `json:"اسم_المستخدم"`
Admin bool `json:"مشرف"`
}
// إنشاء رمز مع مطالب مخصصة
claims := MyClaims{
RegisteredClaims: jwt.RegisteredClaims{},
Username: "اسم_المستخدم_الخاص_بي",
Admin: true,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// المفتاح السري للتوقيع
var mySigningKey = []byte("السر_الخاص_بك٢٥٦_بت")
// توليد الرمز بتنسيق نصي
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("حدث خطأ: %v", err)
}
fmt.Println(strToken)
في الكود أعلاه، قمنا بتعريف هيكل MyClaims
ليحتوي على المطالب المسجلة وبعض المعلومات المخصصة. ثم، لا يزال بإمكاننا استخدام SignedString
لتوليد تمثيل نصي للرمز.
تحليل وتحقق من الرمز
تحليل والتحقق من JWT مهم جدًا، ويمكنك فعل ذلك كالتالي:
// سنستخدم نفس هيكل MyClaims
tokenString := "سلسلة_JWT_الخاصة_بك"
// نحتاج إلى تحديد وظيفة سيستخدمها حزمة jwt لتحليل سلسلة الرمز
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، ويمكننا قراءة المطالب
fmt.Printf("المستخدم: %s، مشرف: %v\n", claims.Username, claims.Admin)