Menu

Sesje i ciasteczka w PHP: session_start i setcookie

Wywołaj session_start() na górze każdej strony, a potem zapisuj wartości w $_SESSION['key']; PHP trzyma je na serwerze i rozpoznaje odwiedzającego po ciasteczku. setcookie('name', 'value', $options) zapisuje małą wartość w przeglądarce, odczytywaną potem z $_COOKIE. Poznaj logowanie, wylogowanie, komunikaty flash, opcje ciasteczek i ustawienia bezpieczeństwa.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Wywołaj session_start() na górze strony, a potem czytaj i zapisuj $_SESSION['key'] jak każdą tablicę: PHP zapisuje ją na serwerze i znajduje ponownie przy następnym żądaniu odwiedzającego. Dla małej wartości, którą ma trzymać przeglądarka, wywołaj setcookie('name', 'value', time() + 86400) i odczytuj ją przy kolejnych żądaniach z $_COOKIE['name'].

<?php
session_start();                        // before any output
setcookie('theme', 'dark', time() + 60 * 60 * 24 * 30);   // 30 days, also before output

$_SESSION['visits'] = ($_SESSION['visits'] ?? 0) + 1;
$theme = $_COOKIE['theme'] ?? 'light';  // set on an earlier visit
echo "You have opened this page {$_SESSION['visits']} times.";

Sesje i ciasteczka mają sens tylko przy kilku żądaniach HTTP z przeglądarki, więc przykłady, które ich wymagają, to zwykły kod. Logika wokół nich to zwykłe PHP na tablicach i ta część działa tutaj: w każdym bloku do uruchomienia $session zastępuje $_SESSION, a każde wywołanie to jedno żądanie.

Jak działa sesja w PHP

  1. Pierwsze session_start() tworzy losowy identyfikator, wysyła go do przeglądarki w ciasteczku o nazwie PHPSESSID i daje ci puste $_SESSION.
  2. Na koniec żądania PHP zapisuje $_SESSION na serwerze (domyślnie w plikach, albo w Redis, albo w bazie danych).
  3. Przy następnym żądaniu przeglądarka odsyła ciasteczko, session_start() odczytuje identyfikator i wczytuje zapisaną tablicę.

Przeglądarka nigdy nie widzi danych, tylko identyfikator. Dlatego identyfikator zalogowanego użytkownika należy do $_SESSION, a nigdy do własnego ciasteczka: odwiedzający może zmienić swoje ciasteczka na dowolną wartość, ale nie może zmienić $_SESSION.

Logowanie i wylogowanie przez sesję

Logowanie to: sprawdź hasło, wygeneruj nowy identyfikator sesji, zapisz identyfikator użytkownika. Wylogowanie to odwrotność i ma trzy kroki.

<?php
// login.php
session_start();

$user = find_user_by_email($_POST['email'] ?? '');
if ($user && password_verify($_POST['password'] ?? '', $user['password_hash'])) {
    session_regenerate_id(true);        // new id after login: stops session fixation
    $_SESSION['user_id'] = $user['id'];
    header('Location: /dashboard.php');
    exit;
}
$error = 'Wrong email or password.';
<?php
// any protected page
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: /login.php');
    exit;
}
<?php
// logout.php
session_start();
$_SESSION = [];                                     // 1. forget the data

$p = session_get_cookie_params();                   // 2. delete the cookie
setcookie(session_name(), '', time() - 3600, $p['path'], $p['domain'], $p['secure'], $p['httponly']);

session_destroy();                                  // 3. delete the stored session
header('Location: /');
exit;

session_regenerate_id(true) ma większe znaczenie, niż się wydaje. Jeśli atakujący zdoła sprawić, że ofiara użyje znanego mu identyfikatora sesji (na przykład przez link), a identyfikator pozostanie ten sam po zalogowaniu, atakujący też jest zalogowany. Nowy identyfikator przy logowaniu zamyka tę lukę. Samo sprawdzanie haseł opisuje strona o password_hash.

Wygaszanie sesji po okresie bezczynności

Własne sprzątanie PHP (session.gc_maxlifetime, domyślnie 1440 sekund) uruchamia się losowo i nie jest niezawodnym limitem czasu. Zapisuj czas ostatniego żądania i sprawdzaj go sam. Blok używa stałych znaczników czasu, żebyś widział każdy przypadek; zmień $timeout albo czasy.

Przerwa od 09:05 do 09:16:40 to 700 sekund, poniżej limitu 15 minut, więc użytkownik zostaje zalogowany. Następna przerwa to 1000 sekund, więc sesja zostaje wyczyszczona.

Komunikaty flash

Komunikat flash jest ustawiany w jednym żądaniu i pokazywany raz w następnym, zwykle „Zapisano.” po przekierowaniu. Zapisz go w sesji, a potem odczytaj i usuń w jednym kroku:

Ustawianie ciasteczka przez setcookie

setcookie() przyjmuje nazwę, wartość i tablicę opcji (PHP 7.3 i nowsze). Ustawiaj te cztery opcje w każdym ciasteczku, chyba że masz powód, by tego nie robić:

<?php
setcookie('theme', 'dark', [
    'expires'  => time() + 60 * 60 * 24 * 30,   // 30 days; 0 means "until the browser closes"
    'path'     => '/',                          // send it on every page, not just this folder
    'secure'   => true,                         // only over HTTPS
    'httponly' => true,                         // JavaScript cannot read it
    'samesite' => 'Lax',                        // not sent on most cross-site requests
]);

$theme = $_COOKIE['theme'] ?? 'light';

Dwie rzeczy zaskakują:

  • $_COOKIE nie zmienia się w tym samym żądaniu. Zawiera to, co przeglądarka wysłała z tym żądaniem. Ciasteczko ustawione teraz pojawi się w $_COOKIE przy następnym.
  • Ciasteczka to nagłówki, więc setcookie() musi wykonać się przed jakimkolwiek wyjściem, dokładnie jak session_start() i header().

Aby usunąć ciasteczko, ustaw je ponownie z tą samą nazwą i path oraz datą wygaśnięcia w przeszłości: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']).

Tablica w ciasteczku

Wartość ciasteczka to string o długości do około 4 KB, więc dane strukturalne zapisuj jako JSON, a to, co wraca, traktuj jako niezaufane: odwiedzający może to zmienić. Nigdy nie wywołuj unserialize() na ciasteczku: może utworzyć obiekty twoich klas i uruchomić ich metody magiczne, a json_decode() zwraca tylko tablice, stringi, liczby, wartości logiczne, null albo stdClass.

Flagi opisuje strona o json_encode. Dla czegokolwiek większego niż kilka krótkich wartości trzymaj dane w sesji (albo w bazie danych), a w ciasteczku zapisuj tylko klucz.

Tokeny „zapamiętaj mnie”

Ciasteczko „zapamiętaj mnie” nie może zawierać identyfikatora użytkownika ani skrótu hasła. Zapisz w ciasteczku długi losowy token, w bazie danych tylko jego skrót, i porównuj przez hash_equals():

Wartości zmieniają się przy każdym uruchomieniu, bo random_bytes() jest losowe. Jeśli baza danych wycieknie, skróty są bezużyteczne bez ciasteczek, a hash_equals() porównuje w stałym czasie, więc sprawdzenie nie zdradza informacji przez czas wykonania.

Sesja vs ciasteczko: czego użyć

Sesja ($_SESSION)Ciasteczko ($_COOKIE)
PrzechowywaneNa serwerzeW przeglądarce
Odwiedzający może odczytać lub zmienićNie (tylko identyfikator)Tak
RozmiarOgraniczony twoim magazynem danychOkoło 4 KB na ciasteczko
TrwaDo zamknięcia przeglądarki lub wygaśnięcia sesjiDo czasu expires
Używaj doZalogowany użytkownik, koszyk, token CSRF, komunikaty flashMotyw, język, zgoda na pliki cookie, token „zapamiętaj mnie”

Częsty błąd: headers already sent

session_start(), setcookie() i header() wysyłają nagłówki HTTP, a nagłówki można wysłać tylko przed treścią. Jakiekolwiek wcześniejsze wyjście, nawet pusta linia przed <?php w dołączonym pliku, daje:

Warning: session_start(): Session cannot be started after headers have already been sent in /var/www/page.php on line 3
Warning: Cannot modify header information - headers already sent by (output started at /var/www/page.php:2) in /var/www/page.php on line 4

Drugi komunikat mówi, gdzie zaczęło się wyjście (page.php:2): przejdź do tej linii i usuń wyjście albo przenieś session_start() nad nią. W plikach zawierających tylko PHP pominięcie zamykającego ?> zapobiega temu, by białe znaki po nim stały się wyjściem.

Najczęściej zadawane pytania

Czym różni się sesja od ciasteczka w PHP?

Ciasteczko jest przechowywane w przeglądarce odwiedzającego i wysyłane z każdym żądaniem, więc odwiedzający może je odczytać i zmienić. Sesja przechowuje dane na serwerze; przeglądarka trzyma tylko losowy identyfikator sesji w ciasteczku (PHPSESSID). Wszystko, czemu trzeba ufać, na przykład identyfikator zalogowanego użytkownika, umieszczaj w $_SESSION.

Jak zniszczyć sesję przy wylogowaniu w PHP?

Opróżnij dane, usuń ciasteczko sesji i zniszcz zapisaną sesję: $_SESSION = [];, potem setcookie(session_name(), '', time() - 3600, '/'), a potem session_destroy();. Następnie przekieruj, żeby kolejna strona zaczęła od zera.

Jak usunąć ciasteczko w PHP?

Ustaw je ponownie z tą samą nazwą, ścieżką i domeną oraz datą wygaśnięcia w przeszłości: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);. Wykonaj też unset($_COOKIE['theme']), jeśli reszta bieżącego skryptu je odczytuje, bo $_COOKIE zmienia się dopiero przy następnym żądaniu.

Jak długo trwa sesja w PHP?

Domyślnie ciasteczko sesji trwa do zamknięcia przeglądarki (session.cookie_lifetime = 0), a serwer może usunąć dane sesji po 1440 sekundach (24 minutach) bez aktywności (session.gc_maxlifetime). Dla stałego limitu zapisuj w sesji czas ostatniego żądania i sprawdzaj go sam.

Dlaczego session_start() zgłasza headers already sent?

Identyfikator sesji wędruje w nagłówku ciasteczka, a nagłówki muszą zostać wysłane przed jakimkolwiek wyjściem. echo, HTML, a nawet pusta linia albo BOM przed <?php wysyłają wyjście jako pierwsze. Wywołuj session_start() na samej górze skryptu, zanim cokolwiek zostanie wypisane.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ