10-bo‘lim

Sessiyalar va cookie

HTTP holatsizligi, sessiya qanday ishlaydi, cookie sozlamalari va xavfsiz kirish tizimi asoslari.

🕑 10 daqiqa o‘qish 📄 691 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo
  2. Sessiya qanday ishlaydi
  3. Sessiya bilan ishlash
  4. Cookie sozlamalari
  5. Sessiya identifikatorini yangilash
  6. Amaliy misol: kirish holati
  7. Xulosa

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 #

PHP
// Har bir misol qat'iy turlar rejimida ishlaydi.
PHP
// 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"]);
}
Natija
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 - serverdagi xotira, brauzerdagi kalit BRAUZER Cookie: PHPSESSID a1b2c3d4e5f6... Faqat KALIT saqlanadi Ma'lumotning o'zi emas Foydalanuvchi ko'ra oladi va o'zgartira oladi har so'rovda kalit yuboriladi SERVER Sessiya saqlanadigan joy a1b2c3... -> ["user_id" => 7, "rol" => "admin"] f9e8d7... -> ["user_id" => 12, "savat" => [...]] Fayl, Redis yoki ma'lumotlar bazasi Ma'lumot SHU YERDA - foydalanuvchi tega olmaydi Shuning uchun huquqlar faqat sessiyada saqlanadi Cookie ga hech qachon huquq yozmang setcookie("rol", "admin") - foydalanuvchi uni brauzerda bir soniyada o'zgartiradi. Sessiya identifikatori - parol bilan teng qiymatli Uni o'g'irlagan odam hisobga to'liq kiradi. Shuning uchun HttpOnly, Secure, SameSite.
Brauzerda kalit, serverda qulf - ma'lumot hech qachon mijozga chiqmaydi

Sessiya bilan ishlash #

PHP
<?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 chiqarmang

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

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

PHP
if (headers_sent($fayl, $qator)) {
    echo "Sarlavhalar {$fayl}:{$qator} da yuborilgan";
}
PHP
// 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);
}
Natija
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 tanlang

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

CookieSameSite
Sessiya (kirish)Lax
CSRF tokeniLax
To'lov/bank amaliStrict
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 #

PHP
// 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";
Natija
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
Kirishdan keyin session_regenerate_id(true) chaqiring

Bu sessiya biriktirish (session fixation) hujumidan himoya qiladi. Hujum shunday ishlaydi:

QadamHujumchi nima qiladi
1Saytga kiradi va PHPSESSID oladi
2Qurbonga shu ID bilan havola yuboradi
3Qurbon o'sha ID bilan tizimga kiradi
4Hujumchi o'sha ID bilan qurbon nomidan ishlaydi

Yechim bir qator:

PHP
// 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:

VaziyatYangilash
Tizimga kirishMajburiy
ChiqishMajburiy (butun sessiyani yo'q qiling)
Huquq o'zgarishi (admin bo'lish)Majburiy
Parol o'zgarishiMajburiy
Oddiy sahifa almashishiKerak emas

Yuqoridagi misolda savat saqlanib qolganiga e'tibor bering: ID o'zgaradi, lekin ma'lumot ko'chiriladi.

Amaliy misol: kirish holati #

PHP
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");
}
Natija
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
Huquqlarni sessiyada emas, har safar bazadan olish

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:

UsulTezlikXavfsizlik
Rolni sessiyada saqlashTezKechikish bor
Har so'rovda bazadan olishSekinroqDarhol 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.

Amaliy topshiriq
  1. Ikki sahifa yarating va sessiyasiz o'zgaruvchi uzatishga urinib ko'ring.
  2. session_start() ni echo dan keyin chaqiring - qanday ogohlantirish chiqadi?
  3. session_set_cookie_params bilan httponly va samesite ni sozlang.
  4. Brauzer vositalarida PHPSESSID cookie sini toping.
  5. Uning qiymatini o'zgartirib ko'ring - nima bo'ladi?
  6. SameSite=Strict bilan boshqa saytdan havola orqali kiring.
  7. Kirish funksiyasida session_regenerate_id(true) ni chaqiring.
  8. ID o'zgargani, lekin savat saqlanganini tekshiring.
  9. Rolga asoslangan huquq tekshiruvchi sinf yozing.
  10. 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.
  • httponly JavaScript dan, secure tarmoqdan, samesite CSRF dan himoya qiladi.
  • Sessiya uchun SameSite=Lax, to'lov amallari uchun Strict.
  • SameSite=None bilan Secure majburiy.
  • 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.

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.