Asosiy kontentga o‘tish

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.

security backend

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:

Python
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)))
Natija
{'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.

Python
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:

Natija
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:

Natija
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:

Python
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:

Natija
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:

Natija
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.

7 daqiqa 74 marta o‘qildi