Maqolalar 7 daqiqa 74 ko‘rish
JWT autentifikatsiya: xavfsiz ishlatish qoidalari
Darsda JWT mavzusiga kelganimizda talabalarning ko‘pchiligi token shifrlangan deb o‘ylaydi. Aslida uning ichini istalgan odam ochib o‘qiydi. Shu farqdan kelib chiqadigan xatolar va ularning oldini olish yo‘llari.
Mundarija
Darsda JWT mavzusiga kelganda men doim bitta narsadan boshlayman: talabadan o‘z tokenini so‘rayman, uni ekranga chiqaramiz va hech qanday kalitsiz, hech qanday hujumsiz ichini ochib o‘qiymiz. Ro‘paramizda foydalanuvchi ID'si, roli va muddati paydo bo‘ladi. Odatda shu yerda xonada bir lahza jimlik bo‘ladi, chunki ko‘pchilik tokenni shifrlangan deb o‘ylab yurgan bo‘ladi.
Aslida JWT hech narsani yashirmaydi. U bitta narsani kafolatlaydi, xolos: token ichidagi ma'lumot yo‘lda o‘zgartirilmagan. Men ko‘rgan JWT xatolarining yarmi aynan shu farqni tushunmaslikdan kelib chiqadi.
Token nimadan iborat#
Har qanday JWT uchta bo‘lakdan tuzilgan va ular nuqta bilan ajratilgan:
header.payload.signature. Birinchi bo‘lakda signature algoritmi yozilgan,
ikkinchisida siz solgan ma'lumot turadi, uchinchisi esa serverning signature'i.
Birinchi ikkitasi shunchaki base64 bilan kodlangan. Base64 — shifrlash emas, u faqat ma'lumotni matn ko‘rinishiga o‘tkazish usuli. Buni o‘zingiz sinab ko‘rishingiz mumkin:
import base64
import json
# To'liq, to'g'ri imzolangan token. Ochish uchun kalit ham, ruxsat ham kerak emas
token = (
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
".eyJzdWIiOiI0MiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc4OTAwMDAwMH0"
".4bcmZFGXqHuar-R_7GriCjA6sXK2e92No6wFG2pDHQo"
)
payload = token.split(".")[1] # o'rta bo'lak
# base64 to'ldiruvchi "=" belgilarini qo'shib qo'yamiz, aks holda xato beradi
payload += "=" * (-len(payload) % 4)
print(json.loads(base64.urlsafe_b64decode(payload)))
{'sub': '42', 'role': 'admin', 'exp': 1789000000}
Bu kodda import jwt ham yo‘q — hech qanday JWT kutubxonasi kerak emas.
Standart base64 yetarli, chunki yashirilgan narsaning o‘zi yo‘q.
Xulosa oddiy: JWT ichiga hech qachon parol, karta raqami yoki boshqa maxfiy ma'lumot solmang. U yerga faqat foydalanuvchi ID'si, roli va muddati kabi "ochiq" narsalar yoziladi.
Unda tokenni nima himoya qiladi#
Signature. Server tokenni private key bilan imzolaydi va har so‘rovda o‘sha
signature'ni qayta hisoblab, mos kelishini tekshiradi. Hujumchi payload'ni o‘zgartirib,
masalan "role": "user" o‘rniga "role": "admin" yozsa, signature mos kelmay
qoladi va server tokenni rad etadi. Ya'ni JWT'ning kuchi maxfiylikda emas,
buzilmaslikda.
import jwt # uv add pyjwt
from datetime import datetime, timedelta, timezone
payload = {
"sub": str(user_id), # satr bo'lishi shart — pastda sababini aytaman
"exp": datetime.now(timezone.utc) + timedelta(minutes=15),
}
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
Ikkita tafsilotga alohida to‘xtalay, chunki ikkalasi ham kishini bir necha soat ovora qilishi mumkin.
Birinchisi — str(user_id). Standart (RFC 7519) sub maydonini satr deb
belgilaydi va PyJWT buni endi qat'iy tekshiradi. Ajablanarlisi shuki, sub
ga butun son bersangiz kodlash bosqichida hech qanday xato chiqmaydi —
token yasaladi, hammasi joyida ko‘rinadi. Xato keyinroq, o‘sha tokenni
o‘qimoqchi bo‘lganingizda chiqadi:
jwt.exceptions.InvalidSubjectError: Subject must be a string
Ikkinchisi — SECRET_KEY ning uzunligi. HS256 uchun kalit kamida 32 bayt
bo‘lishi kerak (RFC 7518, 3.2-bo‘lim), aks holda PyJWT ogohlantiradi:
InsecureKeyLengthWarning: The HMAC key is 17 bytes long, which is below the
minimum recommended length of 32 bytes for SHA256.
Bu shunchaki bezovta qiluvchi yozuv emas: kalit qanchalik qisqa bo‘lsa, uni tanlab topish (brute-force) shunchalik oson, ya'ni hujumchi o‘zi xohlagan tokenni imzolay oladigan bo‘lib qoladi.
Muddat: qisqa access, uzoq refresh#
Endi eng ko‘p uchraydigan amaliy xatoga o‘tamiz. Ko‘pchilik tokenning muddatini bir oy qilib qo‘yadi, chunki foydalanuvchini tez-tez chiqarib yuborish yoqimsiz. Muammo shundaki, bunday token o‘g‘irlansa, hujumchi bir oy davomida bemalol foydalanuvchi nomidan ish qiladi va siz buni to‘xtata olmaysiz.
To‘g‘ri yechim ikki tokenli sxema. Access token qisqa yashaydi, odatda 15–30 daqiqa, va har so‘rovda ishlatiladi. Refresh token uzoq yashaydi, lekin faqat bitta ish uchun kerak: yangi access token olish. Refresh token serverda saqlanadi, demak uni istalgan payt bekor qilish mumkin. Shu bilan ikkala ehtiyoj ham qondiriladi: foydalanuvchi tez-tez login qilmaydi, o‘g‘irlangan access token esa yarim soatdan keyin o‘ladi.
Algoritm chalkashligi#
Bu qism ayniqsa qiziq, chunki hujum kodning o‘zida emas, uni tekshirish usulida yashiringan.
Tokenning birinchi bo‘lagida algoritm nomi yozilgan bo‘ladi va ba'zi
kutubxonalar aynan o‘sha yozuvga ishonadi. Hujumchi buni bilsa, algoritmni
none deb o‘zgartiradi, signature'ni butunlay olib tashlaydi va "signature yo‘q, demak
tekshirishga hojat yo‘q" degan mantiq bilan ichkariga kiradi. Ikkinchi
variant yanada nozik: server RS256 (public/private key juftligi) ishlatsa,
hujumchi algoritmni HS256 ga o‘zgartirib, hammaga ma'lum bo‘lgan public
key bilan imzolaydi. Server esa o‘sha public key'ni private key deb
qabul qilib qo‘yadi.
Ikkala hujumdan ham himoya bitta qatorda:
jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) # faqat HS256
Ya'ni algoritmni tokendan o‘qimaysiz, o‘zingiz qat'iy ko‘rsatasiz. Ikkala
hujumni ham qo‘lda yasab sinab ko‘rdim — alg ni none ga o‘zgartirilgan
token ham, RS256'ning public key'i bilan HS256'da imzolangan token ham
bir xil javob oladi:
jwt.exceptions.InvalidAlgorithmError: The specified alg value is not allowed
PyJWT'da ikkinchi hujumga qarshi yana bitta to‘siq bor: adashib
algorithms=["RS256", "HS256"] deb ikkalasiga ruxsat bergan taqdiringizda
ham u tokenni rad etadi, chunki kalitning o‘zi asimmetrik ekanini
payqaydi:
jwt.exceptions.InvalidKeyError: The specified key is an asymmetric key or
x509 certificate and should not be used as an HMAC secret.
Bu yaxshi, lekin bunga suyanmang — u PyJWT'ning xayrixohligi, standartning
talabi emas. Eski kutubxonalarda va qo‘lda yozilgan tekshiruvlarda bunday
to‘siq bo‘lmaydi, shuning uchun himoya baribir sizning algorithms=[...]
ro‘yxatingiz bo‘lib qolaveradi.
Signature'ni tekshirmaslik — eng qimmat xato
Frontend kodida tokenni signature'siz ochib o‘qish normal holat: brauzer
foydalanuvchi ismini ko‘rsatishi kerak. Xavf backend'da tokenni shunday
o‘qiganda boshlanadi. Agar biror joyda verify_signature=False yozilgan
bo‘lsa, o‘sha endpoint amalda hech qanday himoyasiz — istalgan odam
o‘zi yozgan token bilan kiraveradi.
Buni ham sinab ko‘rdim. Yuqoridagi alg=none tokeni algorithms=["HS256"]
bilan rad etiladi, lekin o‘sha token options={"verify_signature": False}
bilan muammosiz qabul qilinadi — hech qanday kalitsiz, "role": "admin"
bilan birga. Shuning uchun kod ko‘rigida shu satrni qidirishning o‘zi
arziydi.
Tokenni qayerda saqlash kerak#
Brauzer tomonida ikki yo‘l bor va ikkalasining ham o‘z zaifligi mavjud.
localStorage qulay, lekin uni sahifadagi har qanday JavaScript o‘qiy oladi.
Saytga XSS orqali begona skript tushsa, u tokenni bemalol olib chiqib ketadi.
httpOnly cookie'ni esa JavaScript umuman ko‘rmaydi, shuning uchun XSS unga
tegmaydi, lekin buning evaziga CSRF himoyasini qo‘shishga to‘g‘ri keladi.
Amalda ko‘pchilik loyihada ikkinchi yo‘l tanlanadi: refresh token httpOnly
cookie'da yashaydi, access token esa faqat xotirada saqlanadi va sahifa
yangilanganda refresh orqali qaytadan olinadi. Bu yerda tanlov saytingizdagi
qaysi zaiflik ehtimoli yuqoriroq ekaniga bog‘liq —
OWASP ro‘yxati shu savolga javob berishga
yordam beradi.
Eng og‘riqli joy: tokenni bekor qilish#
Endi JWT'ning haqiqiy cheklovi haqida. Sessiya bilan ishlaganda foydalanuvchini tizimdan chiqarish oson: serverdagi yozuvni o‘chirasiz va tamom. JWT esa serverda saqlanmaydi, uni tekshirish uchun bazaga murojaat ham qilinmaydi — butun mohiyati shunda. Demak siz bergan token muddati tugaguncha amal qiladi, xohlaysizmi-yo‘qmi.
Amalda uchta yo‘l ishlatiladi. Birinchisi va eng sodda: access token muddatini shunchalik qisqa qilish (masalan 10 daqiqa), refresh tokenni esa bazada saqlab, kerak bo‘lganda o‘chirish. Ikkinchisi: bekor qilingan tokenlarni Redis'da denylist'ga qo‘yish, lekin bu paytda "serverga murojaat qilmaymiz" degan afzallikning yarmi yo‘qoladi. Uchinchisi: foydalanuvchi yozuviga versiya raqami qo‘shish va uni token ichiga ham yozish; parol o‘zgarganda yoki foydalanuvchi bloklanganda versiya oshiriladi va eski tokenlarning hammasi bir zumda yaroqsiz bo‘ladi.
Qachon JWT umuman kerak emas#
Ochig‘ini aytsam, JWT ko‘p loyihaga keraksiz murakkablik qo‘shadi. Agar sizda oddiy veb-sayt bo‘lsa va foydalanuvchi o‘sha saytning o‘zida ishlasa, Django'ning standart sessiya tizimi hamma jihatdan qulayroq: chiqish bir zumda ishlaydi, huquqlarni o‘zgartirish darhol kuchga kiradi, hech qanday qo‘shimcha kod kerak emas.
JWT o‘zini oqlaydigan joy boshqacha ko‘rinadi: mobil ilova bilan ishlaydigan API, bir nechta alohida servis o‘rtasida bo‘linadigan tizim yoki autentifikatsiyani boshqa xizmatga (masalan Google'ga) topshirgan holat. Ana o‘sha paytda "serverga murojaat qilmasdan tekshirish" haqiqiy foyda beradi.
Qisqasi, tanlovni "hozir hamma JWT ishlatyapti" degan gapga emas, o‘z loyihangizning ehtiyojiga qarab qiling.