10-bo‘lim
Sessiyalar va cookie
HTTP holatsizligi, sessiya qanday ishlaydi, cookie sozlamalari va xavfsiz kirish tizimi asoslari.
Ushbu bo‘lim mundarijasi
HTTP holatsiz protokol: har bir so'rov mustaqil va server sizni eslamaydi. Lekin saytga bir marta kirib, o'nlab sahifani aylanasiz - qanday qilib?
Javob: sessiya.
Muammo #
// Har bir misol qat'iy turlar rejimida ishlaydi.
// Har bir so'rov - toza varaq. Bu oddiy funksiya buni ko'rsatadi.
function sorovniQayta_ishla(array $sorov, array $serverXotirasi): array
{
// Server oldingi so'rov haqida HECH NARSA bilmaydi
return [
"yo'l" => $sorov["yo'l"],
"kim" => $serverXotirasi["kim"] ?? "noma'lum",
"savat_soni" => $serverXotirasi["savat"] ?? 0,
];
}
echo "Xotirasiz (HTTP qanday ishlaydi):\n";
foreach ([["yo'l" => "/kirish"], ["yo'l" => "/savat"], ["yo'l" => "/tolov"]] as $s) {
$n = sorovniQayta_ishla($s, []);
printf(" %-10s kim: %-10s savat: %d\n", $n["yo'l"], $n["kim"], $n["savat_soni"]);
}
echo "\nSessiya bilan (server holatni saqlaydi):\n";
$xotira = ["kim" => "Husanboy", "savat" => 3];
foreach ([["yo'l" => "/kirish"], ["yo'l" => "/savat"], ["yo'l" => "/tolov"]] as $s) {
$n = sorovniQayta_ishla($s, $xotira);
printf(" %-10s kim: %-10s savat: %d\n", $n["yo'l"], $n["kim"], $n["savat_soni"]);
}
Xotirasiz (HTTP qanday ishlaydi):
/kirish kim: noma'lum savat: 0
/savat kim: noma'lum savat: 0
/tolov kim: noma'lum savat: 0
Sessiya bilan (server holatni saqlaydi):
/kirish kim: Husanboy savat: 3
/savat kim: Husanboy savat: 3
/tolov kim: Husanboy savat: 3
Sessiya qanday ishlaydi #
Sessiya bilan ishlash #
<?php
declare(strict_types=1);
// MUHIM: session_start() dan OLDIN hech narsa chiqarilmasin
session_set_cookie_params([
"lifetime" => 0, // brauzer yopilguncha
"path" => "/",
"secure" => true, // faqat HTTPS orqali
"httponly" => true, // JavaScript o'qiy olmaydi
"samesite" => "Lax", // CSRF dan himoya
]);
session_start();
// Yozish
$_SESSION["user_id"] = 7;
$_SESSION["rol"] = "admin";
$_SESSION["oxirgi_faollik"] = time();
// O'qish
$id = $_SESSION["user_id"] ?? null;
$rol = $_SESSION["rol"] ?? "mehmon";
// O'chirish
unset($_SESSION["savat"]);
// Butun sessiyani tugatish (chiqish)
$_SESSION = [];
if (ini_get("session.use_cookies")) {
$p = session_get_cookie_params();
setcookie(session_name(), "", time() - 42000,
$p["path"], $p["domain"], $p["secure"], $p["httponly"]);
}
session_destroy();
session_start() dan oldin hech narsa chiqarmangSessiya cookie HTTP sarlavhasi orqali yuboriladi. Sarlavhalar esa tanadan oldin ketadi.
Agar session_start() dan oldin biror narsa chiqsa -
hatto bitta bo'sh qator ham - PHP shunday deydi:
Warning: session_start(): Cannot start session when
headers already sent
Eng ko'p uchraydigan sabab: fayl boshida <?php dan
oldin bo'sh joy yoki oxirida ?> dan keyingi bo'sh
qator.
Shuning uchun 2-bo'limda aytgan qoida: faqat PHP dan
iborat fayl oxiriga ?> qo'ymang.
Xuddi shu qoida header() va setcookie() uchun ham
amal qiladi.
Tekshirish usuli:
if (headers_sent($fayl, $qator)) {
echo "Sarlavhalar {$fayl}:{$qator} da yuborilgan";
}
Cookie sozlamalari #
// Cookie sozlamalarining ma'nosi
$sozlamalar = [
["httponly", "true", "JavaScript o'qiy olmaydi", "XSS da o'g'irlashdan saqlaydi"],
["secure", "true", "Faqat HTTPS orqali yuboriladi", "Tarmoqda tinglashdan saqlaydi"],
["samesite", "Lax", "Boshqa saytdan yuborilmaydi", "CSRF dan saqlaydi"],
["samesite", "Strict","Umuman boshqa saytdan kelmaydi", "Eng qattiq, lekin noqulay"],
["path", "/", "Qaysi yo'llarda amal qiladi", "Odatda butun sayt"],
["lifetime", "0", "Brauzer yopilguncha", "Kirish uchun to'g'ri tanlov"],
];
printf("%-10s %-8s %-32s %s\n", "SOZLAMA", "QIYMAT", "NIMA QILADI", "NIMADAN SAQLAYDI");
echo str_repeat("-", 92) . "\n";
foreach ($sozlamalar as [$nom, $qiymat, $nima, $nimadan]) {
printf("%-10s %-8s %-32s %s\n", $nom, $qiymat, $nima, $nimadan);
}
echo "\n";
echo "SameSite qiymatlarini solishtirish:\n";
$holatlar = [
["Boshqa saytdagi havolani bosish", "Yuboriladi", "Yuborilmaydi"],
["Boshqa saytdagi forma POST", "Yuborilmaydi", "Yuborilmaydi"],
["Boshqa saytdagi rasm/iframe", "Yuborilmaydi", "Yuborilmaydi"],
["O'z saytimizdagi so'rov", "Yuboriladi", "Yuboriladi"],
];
printf(" %-36s %-14s %s\n", "HOLAT", "Lax", "Strict");
echo " " . str_repeat("-", 66) . "\n";
foreach ($holatlar as [$holat, $lax, $strict]) {
printf(" %-36s %-14s %s\n", $holat, $lax, $strict);
}
SOZLAMA QIYMAT NIMA QILADI NIMADAN SAQLAYDI
--------------------------------------------------------------------------------------------
httponly true JavaScript o'qiy olmaydi XSS da o'g'irlashdan saqlaydi
secure true Faqat HTTPS orqali yuboriladi Tarmoqda tinglashdan saqlaydi
samesite Lax Boshqa saytdan yuborilmaydi CSRF dan saqlaydi
samesite Strict Umuman boshqa saytdan kelmaydi Eng qattiq, lekin noqulay
path / Qaysi yo'llarda amal qiladi Odatda butun sayt
lifetime 0 Brauzer yopilguncha Kirish uchun to'g'ri tanlov
SameSite qiymatlarini solishtirish:
HOLAT Lax Strict
------------------------------------------------------------------
Boshqa saytdagi havolani bosish Yuboriladi Yuborilmaydi
Boshqa saytdagi forma POST Yuborilmaydi Yuborilmaydi
Boshqa saytdagi rasm/iframe Yuborilmaydi Yuborilmaydi
O'z saytimizdagi so'rov Yuboriladi Yuboriladi
SameSite=Strict ni o'ylab tanlangStrict xavfsizroq, lekin bitta noqulaylik bor:
foydalanuvchi boshqa saytdan sizning saytingizga
havola orqali kelsa, cookie yuborilmaydi - ya'ni u
tizimga kirmagandek ko'rinadi.
Tasavvur qiling: mijozga xatda "Buyurtmangizni ko'rish" havolasini yubordingiz. U bosadi va... kirish sahifasini ko'radi. Sahifani yangilasa - kirgan bo'lib chiqadi. Bu chalkash tajriba.
Shuning uchun amaliy tanlov:
| Cookie | SameSite |
|---|---|
| Sessiya (kirish) | Lax |
| CSRF tokeni | Lax |
| To'lov/bank amali | Strict |
| Uchinchi tomon (reklama) | None + Secure |
Lax zamonaviy brauzerlarda standart qiymat va
u CSRF ning asosiy shaklidan - boshqa saytdagi forma
POST idan himoya qiladi.
Diqqat: SameSite=None uchun Secure majburiy.
Usiz brauzer cookie ni umuman qabul qilmaydi.
Sessiya identifikatorini yangilash #
// Kirish jarayonini soddalashtirilgan modelda ko'ramiz
final class SessiyaModeli
{
private array $malumot = [];
private string $id;
private array $tarix = [];
public function __construct()
{
$this->id = $this->yangiId();
$this->tarix[] = $this->id;
}
private function yangiId(): string
{
// Haqiqiy PHP da buni session_regenerate_id() qiladi
return substr(hash("sha256", (string) count($this->tarix) . "urug"), 0, 12);
}
public function idYangila(): void
{
$this->id = $this->yangiId();
$this->tarix[] = $this->id;
}
public function id(): string { return $this->id; }
public function tarix(): array { return $this->tarix; }
public function yoz(string $k, mixed $v): void { $this->malumot[$k] = $v; }
public function oqi(string $k): mixed { return $this->malumot[$k] ?? null; }
}
$s = new SessiyaModeli();
echo "Mehmon sessiyasi: {$s->id()}\n";
$s->yoz("savat", ["klaviatura"]);
echo "\nFoydalanuvchi kirdi - ID NI YANGILAYMIZ:\n";
$s->idYangila();
$s->yoz("user_id", 7);
echo "Kirgandan keyin: {$s->id()}\n";
echo "\nSessiya ID lari tarixi:\n";
foreach ($s->tarix() as $i => $id) {
echo " " . ($i + 1) . ". {$id}\n";
}
echo "\n";
echo "Savat saqlanib qoldi: " . implode(", ", $s->oqi("savat")) . "\n";
echo "Foydalanuvchi id: " . $s->oqi("user_id") . "\n";
Mehmon sessiyasi: 4e8b4cc9c244
Foydalanuvchi kirdi - ID NI YANGILAYMIZ:
Kirgandan keyin: f78f22d68250
Sessiya ID lari tarixi:
1. 4e8b4cc9c244
2. f78f22d68250
Savat saqlanib qoldi: klaviatura
Foydalanuvchi id: 7
session_regenerate_id(true) chaqiringBu sessiya biriktirish (session fixation) hujumidan himoya qiladi. Hujum shunday ishlaydi:
| Qadam | Hujumchi nima qiladi |
|---|---|
| 1 | Saytga kiradi va PHPSESSID oladi |
| 2 | Qurbonga shu ID bilan havola yuboradi |
| 3 | Qurbon o'sha ID bilan tizimga kiradi |
| 4 | Hujumchi o'sha ID bilan qurbon nomidan ishlaydi |
Yechim bir qator:
// Parol tekshirilgandan KEYIN, $_SESSION ga yozishdan OLDIN
session_regenerate_id(true);
$_SESSION["user_id"] = $foydalanuvchi["id"];
true argumenti eski sessiya faylini o'chiradi -
uni albatta bering.
Qachon ID ni yangilash kerak:
| Vaziyat | Yangilash |
|---|---|
| Tizimga kirish | Majburiy |
| Chiqish | Majburiy (butun sessiyani yo'q qiling) |
| Huquq o'zgarishi (admin bo'lish) | Majburiy |
| Parol o'zgarishi | Majburiy |
| Oddiy sahifa almashishi | Kerak emas |
Yuqoridagi misolda savat saqlanib qolganiga e'tibor bering: ID o'zgaradi, lekin ma'lumot ko'chiriladi.
Amaliy misol: kirish holati #
final class KirishHolati
{
public function __construct(private array $sessiya = []) {}
public function kirgan(): bool
{
return isset($this->sessiya["user_id"]);
}
public function rol(): string
{
return $this->sessiya["rol"] ?? "mehmon";
}
public function huquqiBormi(string $amal): bool
{
$huquqlar = [
"mehmon" => ["korish"],
"muallif" => ["korish", "yozish"],
"muharrir" => ["korish", "yozish", "tahrirlash"],
"admin" => ["korish", "yozish", "tahrirlash", "ochirish"],
];
return in_array($amal, $huquqlar[$this->rol()] ?? [], true);
}
public function seansEskirdimi(int $chegara = 1800): bool
{
$oxirgi = $this->sessiya["oxirgi_faollik"] ?? null;
if ($oxirgi === null) {
return true;
}
return (time() - $oxirgi) > $chegara;
}
}
$holatlar = [
"mehmon" => [],
"muallif" => ["user_id" => 3, "rol" => "muallif", "oxirgi_faollik" => time()],
"admin" => ["user_id" => 1, "rol" => "admin", "oxirgi_faollik" => time()],
"eskirgan" => ["user_id" => 5, "rol" => "admin", "oxirgi_faollik" => time() - 7200],
];
$amallar = ["korish", "yozish", "tahrirlash", "ochirish"];
printf("%-10s %-8s", "KIM", "KIRGAN");
foreach ($amallar as $a) {
printf(" %-11s", $a);
}
printf(" %s\n", "ESKIRGAN");
echo str_repeat("-", 76) . "\n";
foreach ($holatlar as $nom => $sessiya) {
$k = new KirishHolati($sessiya);
printf("%-10s %-8s", $nom, $k->kirgan() ? "ha" : "yo'q");
foreach ($amallar as $a) {
printf(" %-11s", $k->huquqiBormi($a) ? "ruxsat" : "-");
}
printf(" %s\n", $k->seansEskirdimi() ? "ha" : "yo'q");
}
KIM KIRGAN korish yozish tahrirlash ochirish ESKIRGAN
----------------------------------------------------------------------------
mehmon yo'q ruxsat - - - ha
muallif ha ruxsat ruxsat - - yo'q
admin ha ruxsat ruxsat ruxsat ruxsat yo'q
eskirgan ha ruxsat ruxsat ruxsat ruxsat ha
Yuqoridagi misolda rol sessiyada saqlangan. Bu tez, lekin bitta muammosi bor: administrator foydalanuvchi huquqini olib qo'ysa, u sessiya tugagunicha eski huquq bilan ishlashda davom etadi.
Ikki yondashuv:
| Usul | Tezlik | Xavfsizlik |
|---|---|---|
| Rolni sessiyada saqlash | Tez | Kechikish bor |
| Har so'rovda bazadan olish | Sekinroq | Darhol amal qiladi |
Amaliy yechim - o'rtacha yo'l: sessiyada faqat
user_id saqlang, rolni esa har so'rovda bazadan
oling. Bitta indeksli so'rov millisekundning ulushini
oladi.
Muhim amallar (to'lov, o'chirish, foydalanuvchi boshqaruvi) uchun esa huquqni har safar tekshiring - sessiyadagi qiymatga tayanmang.
- Ikki sahifa yarating va sessiyasiz o'zgaruvchi uzatishga urinib ko'ring.
session_start()niechodan keyin chaqiring - qanday ogohlantirish chiqadi?session_set_cookie_paramsbilanhttponlyvasamesiteni sozlang.- Brauzer vositalarida
PHPSESSIDcookie sini toping. - Uning qiymatini o'zgartirib ko'ring - nima bo'ladi?
SameSite=Strictbilan boshqa saytdan havola orqali kiring.- Kirish funksiyasida
session_regenerate_id(true)ni chaqiring. - ID o'zgargani, lekin savat saqlanganini tekshiring.
- Rolga asoslangan huquq tekshiruvchi sinf yozing.
- Sessiya eskirganini
time()farqi bilan aniqlang.
Xulosa #
- HTTP holatsiz - server har so'rovni mustaqil ko'radi.
- Sessiya brauzerda faqat kalit (
PHPSESSID), serverda esa ma'lumot saqlaydi. session_start()dan oldin hech narsa chiqarmang - hatto bo'sh qator ham.- Faqat PHP dan iborat fayl oxiriga
?>qo'ymang. - Cookie ga huquq yozmang - foydalanuvchi uni bir soniyada o'zgartiradi.
httponlyJavaScript dan,securetarmoqdan,samesiteCSRF dan himoya qiladi.- Sessiya uchun
SameSite=Lax, to'lov amallari uchunStrict. SameSite=NonebilanSecuremajburiy.- Kirishdan keyin
session_regenerate_id(true)- sessiya biriktirishdan himoya. - ID yangilanganda ma'lumot ko'chiriladi, yo'qolmaydi.
- Muhim amallar uchun huquqni sessiyadan emas, bazadan tekshiring.
Keyingi bo'limda fayllar bilan ishlashni - o'qish, yozish, CSV va xavfsiz yo'llarni 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.