17-bo‘lim
Tokenlar va JWT
O'z ichida ma'lumot saqlaydigan tokenlar - tuzilishi, ikkita klassik hujum va qachon ular kerak emas.
Ushbu bo‘lim mundarijasi
Foydalanuvchi tizimga kirgach, keyingi so'rovlarda uni qanday tanish kerak?
Ikki yondashuv bor va ular butunlay boshqacha ishlaydi.
Ikki xil token #
| Noaniq token | O'z ichida saqlovchi (JWT) | |
|---|---|---|
| Ichida nima bor | Hech narsa - tasodifiy baytlar | Foydalanuvchi ma'lumotlari |
| Tekshirish | Bazadan qidiriladi | Imzo tekshiriladi |
| Bazaga murojaat | Har so'rovda | Kerak emas |
| Bekor qilish | Darhol - qatorni o'chirish | Qiyin |
| O'lcham | 32 bayt | 200-1000 bayt |
| Ma'lumot sizishi | Yo'q | Ichini har kim o'qiy oladi |
function korsat(string $nom, string $qiymat): void
{
printf("%-20s %s%s", $nom . ':', $qiymat, PHP_EOL);
}
function b64url(string $baytlar): string
{
return rtrim(strtr(base64_encode($baytlar), '+/', '-_'), '=');
}
function b64urlOch(string $matn): string
{
return base64_decode(strtr($matn, '-_', '+/'));
}
// Noaniq token - shunchaki tasodifiy baytlar
$noaniq = b64url(random_bytes(32));
// Qiymat har chaqiruvda boshqacha - xossalarini ko'rsatamiz
korsat('uzunligi', strlen($noaniq) . ' belgi');
korsat('entropiya', '32 bayt = 256 bit');
korsat('ichida malumot', 'yoq - u faqat bazadagi kalit');
uzunligi: 43 belgi
entropiya: 32 bayt = 256 bit
ichida malumot: yoq - u faqat bazadagi kalit
JWT tuzilishi #
const JWT_KALIT = 'demo-maxfiy-kalit-hech-qachon-ishlatmang';
function jwtYarat(array $dalillar, string $kalit): string
{
$sarlavha = ['alg' => 'HS256', 'typ' => 'JWT'];
$qismlar = [
b64url(json_encode($sarlavha, JSON_UNESCAPED_SLASHES)),
b64url(json_encode($dalillar, JSON_UNESCAPED_SLASHES)),
];
$imzolanadigan = implode('.', $qismlar);
$imzo = hash_hmac('sha256', $imzolanadigan, $kalit, true);
return $imzolanadigan . '.' . b64url($imzo);
}
// Vaqtlar qat'iy - natija takrorlanishi uchun
$dalillar = [
'sub' => '42',
'name' => 'Husanboy Qodirov',
'rol' => 'user',
'iat' => 1735689600,
'exp' => 1735693200,
];
$token = jwtYarat($dalillar, JWT_KALIT);
$qismlar = explode('.', $token);
korsat('qismlar soni', (string) count($qismlar));
korsat('sarlavha', $qismlar[0]);
korsat('foydali yuk', substr($qismlar[1], 0, 40) . '...');
korsat('imzo', $qismlar[2]);
korsat('umumiy uzunlik', strlen($token) . ' belgi');
qismlar soni: 3
sarlavha: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
foydali yuk: eyJzdWIiOiI0MiIsIm5hbWUiOiJIdXNhbmJveSBR...
imzo: e_yco0SYxtxJW47vVAnoFUDX2V_Zr4SV4xDLm9jj44s
umumiy uzunlik: 195 belgi
Bu eng ko'p uchraydigan chalkashlik. Ichidagi ma'lumotni har kim o'qiy oladi:
$token = jwtYarat([
'sub' => '42',
'name' => 'Husanboy Qodirov',
'rol' => 'user',
'maosh' => 15000000, // BUNI JWT GA QO'YMANG
'iat' => 1735689600,
], JWT_KALIT);
// Kalitsiz, hech qanday sirsiz - shunchaki base64
$qismlar = explode('.', $token);
echo 'Sarlavha:', PHP_EOL;
echo ' ', b64urlOch($qismlar[0]), PHP_EOL;
echo 'Foydali yuk:', PHP_EOL;
echo ' ', b64urlOch($qismlar[1]), PHP_EOL;
Sarlavha:
{"alg":"HS256","typ":"JWT"}
Foydali yuk:
{"sub":"42","name":"Husanboy Qodirov","rol":"user","maosh":15000000,"iat":1735689600}
Imzo faqat o'zgartirishdan himoya qiladi, o'qishdan emas.
| Qo'yish mumkin | Qo'yish mumkin emas |
|---|---|
| Foydalanuvchi identifikatori | Parol yoki uning xeshi |
| Rol nomi | Maosh, tibbiy ma'lumot |
| Amal muddati | Karta raqami |
| Berilgan vaqt | Ichki tizim tafsilotlari |
| Sessiya identifikatori | Maxfiy kalitlar |
Foydalanuvchi o'z tokenini brauzer konsolida ochib ko'radi. Uning hamkasbi ham. Xato hisobotiga tushgan token ham.
Agar maxfiylik kerak bo'lsa - JWE (shifrlangan JWT) ishlatiladi, lekin bu ancha murakkab. Amalda oddiyroq yo'l bor: maxfiy ma'lumotni tokenga umuman qo'ymang, uni bazadan oling.
Tekshirish - va birinchi klassik xato #
// XAVFLI: sarlavhadagi algoritmga ISHONADI
function xavfliTekshir(string $token, string $kalit): array
{
[$s, $y, $i] = explode('.', $token);
$sarlavha = json_decode(b64urlOch($s), true);
// Bu yerda hujumchi algoritmni tanlaydi
if (($sarlavha['alg'] ?? '') === 'none') {
return ['ok' => true, 'sabab' => 'alg=none - imzo tekshirilmadi'];
}
$kutilgan = b64url(hash_hmac('sha256', "{$s}.{$y}", $kalit, true));
return hash_equals($kutilgan, $i)
? ['ok' => true, 'sabab' => 'imzo togri']
: ['ok' => false, 'sabab' => 'imzo notogri'];
}
$token = jwtYarat(['sub' => '42', 'rol' => 'user'], JWT_KALIT);
// 1. Oddiy holat
$n = xavfliTekshir($token, JWT_KALIT);
korsat('togri token', ($n['ok'] ? 'QABUL' : 'RAD') . ' - ' . $n['sabab']);
// 2. Hujumchi rolni o'zgartiradi (imzoni yangilamasdan)
$q = explode('.', $token);
$soxtaYuk = b64url(json_encode(['sub' => '42', 'rol' => 'admin'],
JSON_UNESCAPED_SLASHES));
$soxta = $q[0] . '.' . $soxtaYuk . '.' . $q[2];
$n = xavfliTekshir($soxta, JWT_KALIT);
korsat('yuk ozgartirildi', ($n['ok'] ? 'QABUL' : 'RAD') . ' - ' . $n['sabab']);
// 3. Hujumchi ALGORITMNI o'zgartiradi
$noneSarlavha = b64url(json_encode(['alg' => 'none', 'typ' => 'JWT'],
JSON_UNESCAPED_SLASHES));
$noneToken = $noneSarlavha . '.' . $soxtaYuk . '.';
$n = xavfliTekshir($noneToken, JWT_KALIT);
korsat('alg=none', ($n['ok'] ? 'QABUL' : 'RAD') . ' - ' . $n['sabab']);
togri token: QABUL - imzo togri
yuk ozgartirildi: RAD - imzo notogri
alg=none: QABUL - alg=none - imzo tekshirilmadi
alg=none - JWT tarixidagi eng mashhur xatoUchinchi natijaga qarang: hujumchi imzosiz token yubordi va u qabul qilindi.
U shunchaki ikkita narsani o'zgartirdi:
| Nima | Eski | Yangi |
|---|---|---|
Sarlavhadagi alg | HS256 | none |
| Imzo | 43 belgi | bo'sh |
Va rolni user dan admin ga aylantirdi.
Muammoning ildizi: tekshiruvchi algoritmni tokenning o'zidan oldi. Ya'ni hujumchi qanday tekshirilishini o'zi tanladi.
none algoritmi standartda "imzosiz JWT" uchun
mo'ljallangan edi, lekin ko'p kutubxona uni tekshiruvda
ham qabul qilardi. 2015-yilda bu ommaviy zaiflik sifatida
e'lon qilingan.
Ikkinchi klassik xato: algoritm chalkashligi #
// Server RS256 (ochiq kalitli imzo) ishlatadi deb faraz qilaylik.
// Ochiq kalit hamma uchun MA'LUM.
$ochiqKalit = "-----BEGIN PUBLIC KEY-----\nSOXTA-NAMUNA-KALIT\n-----END PUBLIC KEY-----";
// XAVFLI tekshiruvchi: alg ga qarab usulni tanlaydi
function chalkashTekshir(string $token, string $ochiqKalit): string
{
[$s, $y, $i] = explode('.', $token);
$alg = json_decode(b64urlOch($s), true)['alg'] ?? '';
return match ($alg) {
// HS256 uchun "kalit" sifatida O'SHA ochiq kalitni beradi
'HS256' => hash_equals(
b64url(hash_hmac('sha256', "{$s}.{$y}", $ochiqKalit, true)), $i)
? 'QABUL' : 'RAD',
'RS256' => 'RSA tekshiruvi (bu yerda soddalashtirilgan)',
default => 'RAD: nomalum algoritm',
};
}
// Hujumchi RS256 o'rniga HS256 deb belgilaydi va
// OCHIQ kalitni HMAC siri sifatida ishlatadi
$sarlavha = b64url(json_encode(['alg' => 'HS256', 'typ' => 'JWT'],
JSON_UNESCAPED_SLASHES));
$yuk = b64url(json_encode(['sub' => '42', 'rol' => 'admin'],
JSON_UNESCAPED_SLASHES));
$imzo = b64url(hash_hmac('sha256', "{$sarlavha}.{$yuk}", $ochiqKalit, true));
korsat('hujum natijasi', chalkashTekshir("{$sarlavha}.{$yuk}.{$imzo}",
$ochiqKalit));
hujum natijasi: QABUL
RS256 da ochiq kalit hamma uchun ma'lum - u shunday bo'lishi kerak.
Lekin agar tekshiruvchi alg=HS256 ni ko'rib, o'sha ochiq
kalitni HMAC siri sifatida ishlatsa, hujumchi ham
xuddi shu kalit bilan to'g'ri imzo yasay oladi.
Natijada ochiq kalitli tizim simmetrik tizimga "tushiriladi" va butun himoya yo'qoladi.
Bu algoritm chalkashligi (algorithm confusion) hujumi. 11-bo'limda qisqacha aytib o'tgan edik.
To'g'ri tekshiruvchi #
final class JwtTekshiruvchi
{
// Algoritm KODDA qat'iy - tokendan olinmaydi
private const ALGORITM = 'HS256';
public function __construct(
private readonly string $kalit,
private readonly string $beruvchi,
private readonly string $auditoriya,
) {}
public function tekshir(string $token, int $hozir): array
{
$qismlar = explode('.', $token);
if (count($qismlar) !== 3) {
return ['ok' => false, 'sabab' => 'shakl notogri'];
}
[$s, $y, $i] = $qismlar;
// 1. Sarlavhadagi alg BIZNIKI bilan mos kelishi shart
$sarlavha = json_decode(b64urlOch($s), true);
if (($sarlavha['alg'] ?? '') !== self::ALGORITM) {
return ['ok' => false, 'sabab' => 'algoritm mos emas'];
}
// 2. Imzo - doimiy vaqtda
$kutilgan = b64url(hash_hmac('sha256', "{$s}.{$y}", $this->kalit, true));
if (!hash_equals($kutilgan, $i)) {
return ['ok' => false, 'sabab' => 'imzo notogri'];
}
// 3. Faqat SHUNDAN KEYIN yukni o'qiymiz
$d = json_decode(b64urlOch($y), true);
if (!is_array($d)) {
return ['ok' => false, 'sabab' => 'yuk buzuq'];
}
// 4. Dalillarni tekshiramiz
foreach (['exp', 'iat', 'iss', 'aud', 'sub'] as $majburiy) {
if (!isset($d[$majburiy])) {
return ['ok' => false, 'sabab' => "{$majburiy} yoq"];
}
}
if ($hozir >= $d['exp']) {
return ['ok' => false, 'sabab' => 'muddati otgan'];
}
if (isset($d['nbf']) && $hozir < $d['nbf']) {
return ['ok' => false, 'sabab' => 'hali kuchga kirmagan'];
}
if (!hash_equals($this->beruvchi, (string) $d['iss'])) {
return ['ok' => false, 'sabab' => 'beruvchi mos emas'];
}
if (!hash_equals($this->auditoriya, (string) $d['aud'])) {
return ['ok' => false, 'sabab' => 'auditoriya mos emas'];
}
return ['ok' => true, 'sabab' => 'qabul', 'sub' => $d['sub']];
}
}
$tekshiruvchi = new JwtTekshiruvchi(JWT_KALIT, 'edu.namuna.uz', 'mobil-ilova');
$hozir = 1735689600;
$asos = [
'sub' => '42',
'iss' => 'edu.namuna.uz',
'aud' => 'mobil-ilova',
'iat' => $hozir,
'exp' => $hozir + 3600,
];
$sinovlar = [
'togri token' => jwtYarat($asos, JWT_KALIT),
'boshqa kalit' => jwtYarat($asos, 'boshqa-kalit'),
'muddati otgan' => jwtYarat([...$asos, 'exp' => $hozir - 1], JWT_KALIT),
'boshqa beruvchi' => jwtYarat([...$asos, 'iss' => 'soxta.uz'], JWT_KALIT),
'boshqa auditoriya' => jwtYarat([...$asos, 'aud' => 'veb'], JWT_KALIT),
'exp yoq' => jwtYarat(['sub' => '42', 'iss' => 'edu.namuna.uz',
'aud' => 'mobil-ilova', 'iat' => $hozir], JWT_KALIT),
];
foreach ($sinovlar as $nom => $token) {
$n = $tekshiruvchi->tekshir($token, $hozir);
printf('%-18s %-6s %s%s', $nom, $n['ok'] ? 'QABUL' : 'RAD',
$n['sabab'], PHP_EOL);
}
togri token QABUL qabul
boshqa kalit RAD imzo notogri
muddati otgan RAD muddati otgan
boshqa beruvchi RAD beruvchi mos emas
boshqa auditoriya RAD auditoriya mos emas
exp yoq RAD exp yoq
alg=none va algoritm chalkashligi ham to'xtatiladi:
$tekshiruvchi = new JwtTekshiruvchi(JWT_KALIT, 'edu.namuna.uz', 'mobil-ilova');
$hozir = 1735689600;
$yuk = b64url(json_encode([
'sub' => '1', 'iss' => 'edu.namuna.uz', 'aud' => 'mobil-ilova',
'iat' => $hozir, 'exp' => $hozir + 3600, 'rol' => 'admin',
], JSON_UNESCAPED_SLASHES));
$hujumlar = [
'alg=none' => b64url(json_encode(['alg' => 'none', 'typ' => 'JWT'],
JSON_UNESCAPED_SLASHES)) . '.' . $yuk . '.',
'alg=HS512' => b64url(json_encode(['alg' => 'HS512', 'typ' => 'JWT'],
JSON_UNESCAPED_SLASHES)) . '.' . $yuk . '.aaaa',
'alg=RS256' => b64url(json_encode(['alg' => 'RS256', 'typ' => 'JWT'],
JSON_UNESCAPED_SLASHES)) . '.' . $yuk . '.bbbb',
'imzosiz' => 'x.y',
];
foreach ($hujumlar as $nom => $token) {
$n = $tekshiruvchi->tekshir($token, $hozir);
printf('%-12s %-6s %s%s', $nom, $n['ok'] ? 'QABUL' : 'RAD',
$n['sabab'], PHP_EOL);
}
alg=none RAD algoritm mos emas
alg=HS512 RAD algoritm mos emas
alg=RS256 RAD algoritm mos emas
imzosiz RAD shakl notogri
Bekor qilish muammosi #
$hozir = 1735689600;
// Foydalanuvchi chiqdi. Nima bo'ladi?
$token = jwtYarat([
'sub' => '42', 'iss' => 'edu.namuna.uz', 'aud' => 'mobil-ilova',
'iat' => $hozir, 'exp' => $hozir + 86400, // bir kunlik
], JWT_KALIT);
$tekshiruvchi = new JwtTekshiruvchi(JWT_KALIT, 'edu.namuna.uz', 'mobil-ilova');
korsat('chiqishdan oldin', $tekshiruvchi->tekshir($token, $hozir)['sabab']);
korsat('chiqqandan keyin', $tekshiruvchi->tekshir($token, $hozir + 60)['sabab']);
korsat('parol ozgargach', $tekshiruvchi->tekshir($token, $hozir + 3600)['sabab']);
echo PHP_EOL, 'Token muddati tugagunicha ISHLAYVERADI:', PHP_EOL;
printf(' qolgan vaqt: %d soat%s',
intdiv(86400 - 3600, 3600), PHP_EOL);
chiqishdan oldin: qabul
chiqqandan keyin: qabul
parol ozgargach: qabul
Token muddati tugagunicha ISHLAYVERADI:
qolgan vaqt: 23 soat
Serverda hech qanday holat saqlanmaydi - bu JWT ning asosiy g'oyasi. Lekin aynan shu sabab uni to'xtatib bo'lmaydi.
| Hodisa | Noaniq token | JWT |
|---|---|---|
| Foydalanuvchi chiqdi | Darhol bekor | Ishlayveradi |
| Parol o'zgartirildi | Barcha sessiyalar bekor | Ishlayveradi |
| Hisob bloklandi | Darhol bekor | Ishlayveradi |
| Rol o'zgartirildi | Keyingi so'rovda yangi | Eski rol qoladi |
| Token o'g'irlandi | Bekor qilinadi | Muddatigacha ishlaydi |
Amaliy yechimlar:
| Yechim | Baho |
|---|---|
| Qisqa muddat (5-15 daqiqa) + yangilash tokeni | Eng keng tarqalgan |
Qora ro'yxat (jti bo'yicha) | Ishlaydi, lekin holat qaytadi |
Foydalanuvchi versiyasi (ver dalili) | Bazaga murojaat kerak |
Bekor qilingan vaqt (iat ni solishtirish) | Yengil, keng qo'llanadi |
Ikkinchi va uchinchi qatorga e'tibor bering: ular JWT ning asosiy foydasini - holatsizlikni - bekor qiladi.
Shuning uchun savol tug'iladi: agar baribir bazaga murojaat qilsak, JWT nima uchun kerak?
Qachon JWT, qachon noaniq token #
| Vaziyat | Tanlov |
|---|---|
| Oddiy veb ilova, sessiyalar | Noaniq token + kuki |
| Bitta baza, bitta server | Noaniq token |
| Mikroservislar orasidagi ishonch | JWT |
| Uchinchi tomon API (OAuth) | JWT yoki noaniq |
| Mobil ilova, qisqa sessiya | JWT (qisqa muddat) |
| Bir marta ishlatiladigan havola | Imzolangan URL (5-bo'lim) |
| Darhol bekor qilish kerak | Noaniq token |
// Oddiy veb ilova uchun eng sodda va ishonchli yechim
$sessiyaId = b64url(random_bytes(32));
echo 'Sessiya kukisi sozlamalari:', PHP_EOL;
$sozlamalar = [
'httponly' => 'true - JavaScript oqiy olmaydi',
'secure' => 'true - faqat HTTPS orqali',
'samesite' => 'Lax - CSRF ga qarshi',
'path' => '/',
'domain' => '(qoyilmaydi - subdomenlarga tarqalmasin)',
'lifetime' => '0 - brauzer yopilganda ochadi',
];
foreach ($sozlamalar as $k => $v) {
printf(' %-10s %s%s', $k . ':', $v, PHP_EOL);
}
echo PHP_EOL, 'Bazada saqlanadi: id, foydalanuvchi_id, yaratilgan, ',
'oxirgi_faollik, ip, qurilma', PHP_EOL;
Sessiya kukisi sozlamalari:
httponly: true - JavaScript oqiy olmaydi
secure: true - faqat HTTPS orqali
samesite: Lax - CSRF ga qarshi
path: /
domain: (qoyilmaydi - subdomenlarga tarqalmasin)
lifetime: 0 - brauzer yopilganda ochadi
Bazada saqlanadi: id, foydalanuvchi_id, yaratilgan, oxirgi_faollik, ip, qurilma
Ko'p loyihada JWT kerak bo'lmagan joyda ishlatiladi.
Belgilari:
| Belgi | Ma'nosi |
|---|---|
| Bitta backend, bitta baza | Holatsizlik foyda bermaydi |
| Har so'rovda baribir foydalanuvchi o'qiladi | Bazaga murojaat baribir bor |
| Qora ro'yxat qo'shilgan | Holat qaytdi - JWT ma'nosini yo'qotdi |
| Token kukida saqlanadi | Sessiya kukisi bilan bir xil, lekin murakkabroq |
Bunday holatda oddiy sessiya:
- Kamroq kod
- Darhol bekor qilinadi
- Ma'lumot sizmaydi
- Xato qilish qiyinroq
JWT taqsimlangan tizimlarda, xizmatlar bir-biriga ishonishi kerak bo'lganda kuchli. Bitta ilovada esa ko'pincha ortiqcha.
- Noaniq token va JWT farqini jadval qilib yozing.
- JWT ni qo'lda yarating (sarlavha, yuk, HMAC imzo).
- Uning ichini kalitsiz o'qing - nima ko'rindi?
- JWT ga qo'yish mumkin bo'lmagan beshta ma'lumotni sanang.
alg=nonehujumini takrorlang va zaif tekshiruvchi uni qabul qilishini ko'ring.- Algoritm chalkashligi hujumini takrorlang.
- Algoritmni kodda qat'iy belgilaydigan tekshiruvchi yozing.
exp,iss,audtekshiruvlarini qo'shing va oltita holatni sinang.- Foydalanuvchi chiqqandan keyin token ishlayverishini ko'rsating.
- O'z loyihangizda JWT haqiqatan kerakmi - javob yozing.
Xulosa #
- Noaniq token - tasodifiy baytlar; ma'no bazada.
- JWT - o'z ichida ma'lumot saqlaydi va imzolangan.
- JWT shifrlanmagan: ichini har kim base64 dan ochib o'qiy oladi.
- Maxfiy ma'lumotni tokenga qo'ymang - uni bazadan oling.
alg=none- eng mashhur JWT zaifligi: imzo tekshirilmaydi.- Algoritm chalkashligi - ochiq kalit HMAC siri sifatida ishlatiladi.
- Ikkala hujumning sababi bitta: algoritm tokendan olingan.
- Algoritm kodda qat'iy belgilanishi shart.
- Tekshirish tartibi: algoritm → imzo → keyin yukni o'qish → dalillar.
- JWT ni bekor qilib bo'lmaydi - qisqa muddat va yangilash tokeni kerak.
Keyingi bo'limda hamma narsani ushlab turadigan halqani - kalitlarni boshqarish ni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.