معرفی JWT

JWT یا (JSON Web Token) یک روش مختصر و خودبخودی برای احراز هویت در وب است. این شامل سه قسمت است: هدر، بسته داده و امضا. هدر حاوی نوع توکن و اطلاعات الگوریتم رمزنگاری است، بسته داده داده‌هایی که قرار است منتقل شوند، و امضا برای تأیید صحت و اعتبار توکن استفاده می‌شود.

JWT اصولاً در دو حالت استفاده می‌شود:

  1. احراز هویت: هنگامی که یک کاربر وارد شود، درخواست‌های بعدی شامل یک JWT خواهد بود که به کاربر اجازه دسترسی به مسیرها، سرویس‌ها و منابع مجاز را می‌دهد.
  2. تبادل اطلاعات: JWT روش مناسبی برای انتقال امن اطلاعات بین اطراف است؛ زیرا می‌توانند به صورت دیجیتال امضا شود، به عنوان مثال با استفاده از جفت کلید عمومی/خصوصی.

فرمت داده JWT

JWT یک روش فشرده و امن برای نمایش اطلاعات جهت تبادل بین اطراف است. یک JWT در واقع از سه بخش تشکیل شده است که با نقطه (.) از یکدیگر جدا می‌شوند؛ به ترتیب هدر.بسته داده.امضا. در ادامه جزئیات فرمت داده این سه بخش را توضیح خواهیم داد.

1. هدر

هدر معمولاً از دو بخش تشکیل شده است: نوع توکن – معمولاً JWT، و الگوریتم امضا یا رمزنگاری استفاده شده مانند HMAC SHA256 یا RSA. هدر به صورت JSON نمایش داده شده و سپس به یک رشته با استفاده از Base64Url تبدیل می‌شود. به عنوان مثال:

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

بعد از تبدیل، ممکن است یک رشته مشابه این دریافت کنید:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. بسته داده

بسته داده شامل یک سری ادعاها یا اطلاعات درباره موجودیت (معمولاً یک کاربر) و داده‌های دیگر است. بسته داده ممکن است شامل چندین ادعای پیش‌تعیین شده (همچنین به نام ادعاهای ثبت شده شناخته می‌شوند) و ادعاهای سفارشی (ادعاهای خصوصی) باشد.

ادعاهای پیش‌تعیین شده ممکن است شامل:

  • 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

کد شبیه‌سازی برای امضا کردن آن‌ها با استفاده از یک کلید ممکن است به این صورت باشد:

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 خود قرار دهید:

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

ایجاد یک توکن ساده

برای ایجاد یک توکن JWT ساده با استفاده از زبان Go و الگوریتم HS256، باید مراحل زیر را دنبال کنید:

اول، یک شیء توکن جدید با استفاده از الگوریتم 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 حمل اطلاعات است. این اطلاعات از طریق ادعاها در توکن کدگذاری می‌شوند. به عنوان مثال، بیایید ادعاهای سفارشی بسازیم:

// ساختار ادعاهای سفارشی
type MyClaims struct {
	jwt.RegisteredClaims
	Username string `json:"username"`
	Admin    bool   `json:"admin"`
}

// ایجاد یک توکن با ادعاهای سفارشی
claims := MyClaims{
    RegisteredClaims: jwt.RegisteredClaims{},
    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 := "رشته-JWT-شما"

// باید یک تابع را تعریف کنیم که بسته jwt برای تجزیه توکنString به کار ببرد
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("توکن نامعتبر است")
}

// در این مرحله، توکن تجزیه‌شده تأیید شده است و می‌توانیم به ادعاها دسترسی پیدا کنیم
fmt.Printf("کاربر: %s, مدیر: %v\n", claims.Username, claims.Admin)

در کد بالا، رشته توکن، نمونه‌ی MyClaims، و تابع کلید keyFunc را به تابع jwt.ParseWithClaims ارسال کردیم. این تابع امضای توکن را تأیید می‌کند و توکن را تجزیه می‌کند، اگر توکن معتبر باشد متغیر claims را پُر می‌کند.