JWT yaratish va tekshirish
JWT (JSON Web Token) — ikki tomon o‘rtasida ma’lumotni xavfsiz uzatish uchun ishlatiladigan ixcham token formati. API’larda u ko‘pincha access token sifatida qo‘llanadi: login’dan keyin server JWT beradi, klient esa uni har bir so‘rovda Authorization: Bearer sarlavhasida qaytaradi.
Session va Token-based auth farqi darsida token imzolangan guvohnomaga o‘xshatilgan edi. Bu darsda o‘sha guvohnoma ichida nima yozilganini, imzo qanday qo‘yilishini va qanday tekshirilishini ko‘ramiz.
JWT tuzilishi
JWT nuqta bilan ajratilgan uchta qismdan iborat:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBeml6YSIsImV4cCI6MTc1OTIzMjgwMH0.4Hb6nX0cQ2kS7tZqL8mR1vYwJ3fE9pA5oU2iT6gD0hM
└──────────────┬─────────────────────┘ └──────────────────────────┬──────────────────────────────┘ └──────────────────┬───────────────────────┘
header payload signature
Header — token turi va imzo algoritmi:
Payload — token ichidagi ma’lumotlar, ya’ni claim'lar:
Signature — header va payload’dan maxfiy kalit yordamida hisoblangan imzo:
Header va payload Base64URL bilan kodlangan. Bu shifrlash emas. Istalgan odam tokenni ochib, ichini o‘qiy oladi:
Danger
JWT payload’iga parol, karta raqami, pasport ma’lumoti yoki boshqa maxfiy narsa yozmang. Token brauzer xotirasida, loglarda va proxy serverlarda qolishi mumkin. Imzo faqat ma’lumotni o‘zgartirishdan himoya qiladi, uni o‘qishdan emas.
Imzo nimani kafolatlaydi?
Hujumchi payload’dagi "sub":"42" qiymatini "sub":"1"ga, ya’ni administrator ID’siga almashtirib ko‘rdi deylik. Payload o‘zgargani uchun imzo endi unga mos kelmaydi. Yangi imzo yaratish uchun esa maxfiy kalit kerak, u faqat serverda bor. Server imzoni tekshirib, soxta tokenni rad etadi.
Standart claim’lar
JWT standarti (RFC 7519) bir nechta claim nomlarini belgilaydi:
| Claim | Nomi | Ma’nosi |
|---|---|---|
sub |
Subject | Token kimga tegishli, odatda foydalanuvchi ID’si |
exp |
Expiration Time | Qachon muddati tugaydi (Unix vaqt, soniyalarda) |
iat |
Issued At | Qachon yaratilgan |
nbf |
Not Before | Qachondan boshlab haqiqiy |
iss |
Issuer | Kim yaratgan, masalan books-api |
aud |
Audience | Kim uchun mo‘ljallangan |
jti |
JWT ID | Tokenning noyob ID’si. Bekor qilish uchun kerak |
Standart claim’lardan tashqari o‘z claim’laringizni ham qo‘shish mumkin, masalan role. Lekin tokenni katta qilib yubormang, u har bir so‘rovda yuboriladi.
Algoritm tanlash
| Algoritm | Turi | Qachon |
|---|---|---|
HS256 |
Simmetrik (HMAC): bitta maxfiy kalit | Token yaratadigan va tekshiradigan servis bitta bo‘lsa |
RS256, ES256, EdDSA |
Asimmetrik: yopiq kalit bilan imzolanadi, ochiq kalit bilan tekshiriladi | Token bir servisda yaratilib, ko‘p servislarda tekshirilsa |
Bitta API uchun HS256 yetarli va sodda. Asimmetrik algoritmda boshqa servislar tokenni ochiq kalit bilan tekshira oladi, lekin yangi token yarata olmaydi. Shuning uchun mikroservislarda ular afzal.
HS256 uchun kalit uzun va tasodifiy bo‘lishi kerak. Kamida 32 bayt:
golang-jwt kutubxonasi
Go’da JWT bilan ishlash uchun keng qo‘llanadigan kutubxona — github.com/golang-jwt/jwt:
Claim’lar turi
jwt.RegisteredClaims standart claim’larni (sub, exp, iat va boshqalar) o‘z ichiga oladi. U struct ichiga nomsiz joylashtirilgan, shuning uchun uning maydonlari to‘g‘ridan-to‘g‘ri Claims darajasida ko‘rinadi. Role esa bizning qo‘shimcha claim’imiz.
Token yaratish
func (s *TokenService) Issue(userID int64, role string) (string, error) {
now := s.now()
claims := Claims{
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
Subject: strconv.FormatInt(userID, 10),
Issuer: s.issuer,
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(s.ttl)),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(s.secret)
}
jwt.NewWithClaims header va payload’ni tayyorlaydi. SignedString ularni kodlaydi, imzolaydi va tayyor satrni qaytaradi. HS256 uchun kalit []byte ko‘rinishida beriladi.
Tokenni tekshirish
func (s *TokenService) Parse(tokenStr string) (*Claims, error) {
claims := &Claims{}
_, err := jwt.ParseWithClaims(tokenStr, claims,
func(t *jwt.Token) (any, error) { return s.secret, nil },
jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}),
jwt.WithIssuer(s.issuer),
jwt.WithExpirationRequired(),
jwt.WithLeeway(30*time.Second),
jwt.WithTimeFunc(s.now),
)
if err != nil {
return nil, err
}
return claims, nil
}
ParseWithClaims uch ishni bajaradi:
- Tokenni qismlarga ajratadi va JSON’ni
claimsga o‘qiydi. - Uchinchi argumentdagi funksiya (
Keyfunc) qaytargan kalit bilan imzoni tekshiradi. exp,issva boshqa claim’larni opsiyalarga ko‘ra tekshiradi.
Har bir opsiya nima uchun kerak:
WithValidMethods— eng muhimi. Tokenning header qismida algoritm yozilgan va uni hujumchi o‘zgartirishi mumkin. Ma’lum hujumlardan birida"alg": "none"yoziladi, ya’ni "imzo yo‘q". Boshqasida esa asimmetrik va simmetrik algoritmlar almashtiriladi. Faqat kutilgan algoritmga ruxsat berilsa, bu hujumlar ishlamaydi.WithExpirationRequired—expclaim’i bo‘lmagan tokenlarni rad etadi. Aks holda muddatsiz token cheksiz ishlaydi.WithIssuer— token aynan bizning servis tomonidan yaratilganini tekshiradi. Bir xil kalitni ishlatgan boshqa servisning tokeni qabul qilinmaydi.WithLeeway— serverlar soatlari orasidagi kichik farqni hisobga oladi.WithTimeFunc— joriy vaqtni beradigan funksiya. Testlarda vaqtni boshqarish uchun qulay.
Xatolarni ajratish
golang-jwt xato turlarini errors.Is bilan tekshirish imkonini beradi:
switch {
case errors.Is(err, jwt.ErrTokenExpired):
// muddati tugagan → klient tokenni yangilashi kerak
case errors.Is(err, jwt.ErrTokenSignatureInvalid):
// imzo noto‘g‘ri → soxta token
case errors.Is(err, jwt.ErrTokenMalformed):
// token formati buzilgan
}
Klientga barcha holatlarda 401 qaytariladi. Lekin muddati tugagan token haqida alohida kod bilan xabar berish foydali: klient buni ko‘rib, refresh token orqali yangi token so‘raydi.
To‘liq misol
| main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...
1) token yaroqli: sub=42 role=reader
2) imzo noto‘g‘ri
3) imzo noto‘g‘ri
4) imzo noto‘g‘ri
5) token muddati tugagan
6) token formati buzilgan
Har bir hujum rad etildi:
- 2-holat. Payload
role: adminqilib o‘zgartirildi, lekin imzo eski qoldi. Imzo mos kelmadi. - 3-holat. Token to‘g‘ri tuzilgan, lekin boshqa kalit bilan imzolangan.
- 4-holat. Imzosiz (
alg: none) tokenWithValidMethodstufayli rad etildi. Kutubxona ruxsat etilmagan algoritmni ham imzo xatosi sifatida qaytaradi. - 5-holat. Token 15 daqiqa yashaydi, soat esa 20 daqiqa oldinga surildi.
Login’ga token qo‘shish
Oldingi darsdagi login handleri endi token qaytaradi:
func (h *Handler) login(w http.ResponseWriter, r *http.Request) {
// ... email va parol tekshiruvi
token, err := h.tokens.Issue(user.ID, user.Role)
if err != nil {
log.Printf("token yaratish: %v", err)
writeError(w, http.StatusInternalServerError, "internal", "ichki server xatosi")
return
}
w.Header().Set("Cache-Control", "no-store")
writeJSON(w, http.StatusOK, map[string]any{
"access_token": token,
"token_type": "Bearer",
"expires_in": int(h.tokens.ttl.Seconds()),
"user": toResponse(user),
})
}
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicmVhZGVyIiwiaXNzIjoiYm9va3MtYXBpIiwic3ViIjoiMSIsImV4cCI6MTc1OTIyNzMwMCwiaWF0IjoxNzU5MjI2NDAwfQ.9m1c...",
"token_type": "Bearer",
"expires_in": 900,
"user": {"id": 1, "email": "[email protected]", "name": "Aziza", "role": "reader", "created_at": "..."}
}
token_typevaexpires_inmaydonlari OAuth 2.0 standartidagi javob formatiga mos keladi. Klient kutubxonalari bu formatni yaxshi tushunadi.Cache-Control: no-storetoken brauzer yoki proxy keshida saqlanib qolmasligini ta’minlaydi.
Access tokenning muddati
Muddat qanchalik qisqa bo‘lsa, o‘g‘irlangan token shunchalik kam vaqt ishlaydi. Lekin juda qisqa muddat foydalanuvchini tez-tez qayta kiritishga majbur qiladi. Amaliyotda:
- access token — 5 daqiqadan 1 soatgacha, odatda 15 daqiqa;
- refresh token — bir necha kundan bir necha haftagacha. U serverda saqlanadi va bekor qilinishi mumkin.
Refresh token oqimi bu kursda to‘liq yozilmaydi, lekin uning mantiqi Session va Token-based auth farqi darsida tushuntirilgan.
Xulosa
- JWT uch qismdan iborat: header, payload va imzo. Header va payload shifrlanmagan, faqat kodlangan.
- Payload’ga maxfiy ma’lumot yozilmaydi.
- Imzo tokenni o‘zgartirishdan himoya qiladi.
HS256uchun kamida 32 baytlik tasodifiy kalit kerak. - Tekshirishda
WithValidMethodsbilan algoritmni qat’iy belgilang vaexpclaim’ini majburiy qiling. - Xato turlari
errors.Isbilan ajratiladi. Muddati tugagan token alohida qayta ishlanadi. - Access token qisqa muddatli bo‘ladi.
Keyingi darsda tokenni har bir so‘rovda tekshiradigan auth middleware yozamiz va foydalanuvchini contextga joylaymiz.