Lekcja 17: Sesje i Ciasteczka - Zarządzanie Stanem Użytkownika
Witaj w siedemnastej lekcji kursu PHP! HTTP, protokół na którym opiera się World Wide Web, jest z natury protokołem bezstanowym (stateless). Oznacza to, że każde żądanie od klienta (przeglądarki) do serwera jest traktowane jako niezależna transakcja, a serwer nie pamięta poprzednich interakcji z tym samym klientem. Jednak wiele aplikacji internetowych, takich jak sklepy e-commerce, fora dyskusyjne czy systemy logowania, wymaga utrzymania informacji o stanie użytkownika pomiędzy kolejnymi żądaniami – np. co użytkownik ma w koszyku, czy jest zalogowany, jakie ma preferencje. PHP oferuje dwa główne mechanizmy do zarządzania stanem użytkownika: sesje i ciasteczka (cookies). W tej lekcji dokładnie omówimy oba te mechanizmy, ich działanie, zastosowania oraz aspekty bezpieczeństwa.
Ciasteczka (Cookies)
Ciasteczka to małe fragmenty danych (pliki tekstowe), które serwer wysyła do przeglądarki użytkownika, a przeglądarka przechowuje je na komputerze klienta i odsyła z powrotem do serwera przy każdym kolejnym żądaniu do tej samej domeny. Pozwalają one "zapamiętać" pewne informacje o użytkowniku lub jego aktywności.
Jak Działają Ciasteczka?
- Serwer wysyła ciasteczko: Gdy użytkownik odwiedza stronę po raz pierwszy lub wykonuje akcję, która wymaga zapamiętania informacji, serwer może wysłać ciasteczko do przeglądarki za pomocą nagłówka HTTP
Set-Cookie
. - Przeglądarka przechowuje ciasteczko: Przeglądarka zapisuje ciasteczko na dysku lokalnym użytkownika (lub w pamięci, jeśli to ciasteczko sesyjne bez daty wygaśnięcia).
- Przeglądarka odsyła ciasteczko: Przy każdym kolejnym żądaniu do tej samej domeny (i ścieżki, jeśli została określona), przeglądarka automatycznie dołącza odpowiednie ciasteczka do nagłówka HTTP
Cookie
. - Serwer odczytuje ciasteczko: Skrypt PHP na serwerze może odczytać wartości ciasteczek z superglobalnej tablicy
$_COOKIE
.
Ustawianie Ciasteczek w PHP - Funkcja setcookie()
W PHP ciasteczka ustawia się za pomocą funkcji setcookie()
. Musi ona zostać wywołana przed jakimkolwiek innym wyjściem do przeglądarki (przed jakimkolwiek kodem HTML, echem, spacjami itp.), ponieważ modyfikuje nagłówki HTTP.
Składnia funkcji setcookie()
:
<?php
bool setcookie (
string $name, // Nazwa ciasteczka
string $value = "", // Wartość ciasteczka
int $expires_or_options = 0, // Czas wygaśnięcia (timestamp Unix) lub tablica opcji (od PHP 7.3)
string $path = "", // Ścieżka na serwerze, dla której ciasteczko będzie dostępne
string $domain = "", // Domena, dla której ciasteczko będzie dostępne
bool $secure = false, // Czy ciasteczko ma być wysyłane tylko przez HTTPS?
bool $httponly = false // Czy ciasteczko ma być dostępne tylko przez protokół HTTP (niedostępne dla JavaScript)?
);
?>
Parametry:
$name
: Nazwa ciasteczka. Używana jako klucz w tablicy$_COOKIE
.$value
: Wartość ciasteczka. Przechowywana na komputerze klienta. Zaleca się, aby wartości były kodowane (np. za pomocąurlencode()
), jeśli zawierają specjalne znaki, chociażsetcookie()
automatycznie wykonuje urlencode dla wartości.$expires_or_options
:- Czas wygaśnięcia (timestamp Unix): Jeśli ustawiony na 0 lub pominięty, ciasteczko wygasa po zamknięciu przeglądarki (staje się ciasteczkiem sesyjnym). Aby ustawić ciasteczko na określony czas, podaje się przyszły timestamp, np.
time() + 3600
(na godzinę),time() + (86400 * 30)
(na 30 dni). - Tablica opcji (od PHP 7.3): Można przekazać tablicę asocjacyjną z opcjami:
"expires"
,"path"
,"domain"
,"secure"
,"httponly"
, oraz"samesite"
(np."Strict"
,"Lax"
,"None"
).
- Czas wygaśnięcia (timestamp Unix): Jeśli ustawiony na 0 lub pominięty, ciasteczko wygasa po zamknięciu przeglądarki (staje się ciasteczkiem sesyjnym). Aby ustawić ciasteczko na określony czas, podaje się przyszły timestamp, np.
$path
: Ścieżka na serwerze, dla której ciasteczko będzie dostępne. Jeśli ustawiona na"/"
, ciasteczko będzie dostępne dla całej domeny. Jeśli np."/sklep/"
, to tylko dla stron w tym katalogu i jego podkatalogach. Domyślnie jest to bieżący katalog, w którym skrypt jest wykonywany.$domain
: Domena, dla której ciasteczko jest dostępne. Aby ciasteczko było dostępne dla wszystkich subdomen (np.sklep.example.com
iblog.example.com
), można ustawić domenę na".example.com"
(z kropką na początku). Domyślnie jest to nazwa hosta bieżącej domeny.$secure
: Jeśli ustawione natrue
, ciasteczko będzie wysyłane przez przeglądarkę tylko wtedy, gdy połączenie jest szyfrowane (HTTPS). Zalecane dla wrażliwych danych.$httponly
: Jeśli ustawione natrue
, ciasteczko nie będzie dostępne dla skryptów po stronie klienta (np. JavaScript przezdocument.cookie
). Pomaga to w ochronie przed atakami XSS (Cross-Site Scripting) próbującymi wykraść ciasteczka. Zdecydowanie zalecane.samesite
(w tablicy opcji): Kontroluje, kiedy ciasteczko jest wysyłane z żądaniami cross-site. Możliwe wartości:"Strict"
(tylko dla żądań z tej samej strony),"Lax"
(domyślnie w wielu przeglądarkach; wysyłane przy nawigacji najwyższego poziomu i bezpiecznych metodach HTTP GET),"None"
(wysyłane z wszystkimi żądaniami cross-site; wymaga atrybutuSecure
). Pomaga w ochronie przed atakami CSRF (Cross-Site Request Forgery).
<?php
// Pamiętaj, że setcookie() musi być wywołane przed jakimkolwiek outputem!
// Ustawienie prostego ciasteczka, które wygaśnie po zamknięciu przeglądarki
setcookie("uzytkownik", "JanKowalski");
// Ustawienie ciasteczka na godzinę
$czas_wygasniecia = time() + 3600; // obecny czas + 1 godzina (3600 sekund)
setcookie("ostatnia_wizyta", date("Y-m-d H:i:s"), $czas_wygasniecia);
// Ustawienie ciasteczka z większą liczbą opcji (od PHP 7.3 używając tablicy opcji)
$opcje_ciasteczka = [
"expires" => time() + (86400 * 30), // na 30 dni
"path" => "/",
"domain" => "", // Pusta wartość oznacza bieżącą domenę
"secure" => true, // Tylko przez HTTPS
"httponly" => true, // Niedostępne dla JavaScript
"samesite" => "Lax" // Ochrona CSRF
];
setcookie("preferencje_koloru", "ciemny_motyw", $opcje_ciasteczka);
// Jeśli używasz PHP < 7.3, opcje przekazujesz jako osobne argumenty:
// setcookie("preferencje_koloru", "ciemny_motyw", time() + (86400 * 30), "/", "", true, true);
echo "Ciasteczka zostały (lub zostaną) ustawione. Odśwież stronę, aby je zobaczyć.";
?>
Odczytywanie Ciasteczek - Superglobalna Tablica $_COOKIE
Wartości ustawionych ciasteczek są dostępne w skrypcie PHP za pośrednictwem superglobalnej tablicy asocjacyjnej $_COOKIE
. Tablica ta jest wypełniana na początku działania skryptu, na podstawie ciasteczek przesłanych przez przeglądarkę.
<?php
// Sprawdzenie, czy ciasteczko "uzytkownik" istnieje
if (isset($_COOKIE["uzytkownik"])) {
$nazwa_uzytkownika = htmlspecialchars($_COOKIE["uzytkownik"]); // Zawsze oczyszczaj dane wyjściowe!
echo "Witaj ponownie, $nazwa_uzytkownika!<br>";
} else {
echo "Witaj, gościu! Wygląda na to, że jesteś tu po raz pierwszy (lub ciasteczka są wyłączone/usunięte).<br>";
}
if (isset($_COOKIE["ostatnia_wizyta"])) {
echo "Twoja ostatnia wizyta (wg ciasteczka): " . htmlspecialchars($_COOKIE["ostatnia_wizyta"]) . "<br>";
}
if (isset($_COOKIE["preferencje_koloru"])) {
echo "Twoje preferencje koloru: " . htmlspecialchars($_COOKIE["preferencje_koloru"]) . "<br>";
}
// Wyświetlenie wszystkich ciasteczek (do celów debugowania)
// echo "<h3>Wszystkie ciasteczka (".htmlspecialchars("$_COOKIE")."):</h3><pre>";
// print_r($_COOKIE);
// echo "</pre>";
?>
Ważne: Ciasteczka ustawione za pomocą setcookie()
w danym skrypcie nie będą dostępne w tablicy $_COOKIE
podczas tego samego wykonania skryptu. Będą dostępne dopiero przy następnym żądaniu HTTP, gdy przeglądarka je odeśle.
Modyfikowanie i Usuwanie Ciasteczek
Modyfikowanie ciasteczka odbywa się poprzez ponowne wywołanie setcookie()
z tą samą nazwą, ale nową wartością i/lub nowymi opcjami. Wszystkie argumenty (ścieżka, domena itp.) powinny być takie same jak przy oryginalnym ustawieniu, chyba że celowo chcemy je zmienić.
Usuwanie ciasteczka polega na ustawieniu go z datą wygaśnięcia w przeszłości. Najprościej jest użyć time() - 3600
(lub dowolnej innej wartości ujemnej lub mniejszej od obecnego czasu). Należy również podać te same parametry path
i domain
, które były użyte przy tworzeniu ciasteczka.
<?php
// Załóżmy, że ciasteczko "uzytkownik" już istnieje
// Modyfikacja wartości ciasteczka "uzytkownik"
// setcookie("uzytkownik", "ZmienionyJanKowalski", time() + 3600, "/");
// Usuwanie ciasteczka "uzytkownik"
// Należy podać te same parametry path i domain, co przy tworzeniu
setcookie("uzytkownik", "", time() - 3600, "/"); // Ustawienie wartości na pustą i czas wygaśnięcia w przeszłości
// Usuwanie ciasteczka "ostatnia_wizyta"
setcookie("ostatnia_wizyta", "", time() - 3600, "/");
// Usuwanie ciasteczka "preferencje_koloru" (z opcjami)
$opcje_usuniecia = [
"expires" => time() - 3600,
"path" => "/",
"domain" => "",
"secure" => true,
"httponly" => true,
"samesite" => "Lax"
];
setcookie("preferencje_koloru", "", $opcje_usuniecia);
echo "Ciasteczko \"uzytkownik\" zostało usunięte (lub jego usunięcie zostało zlecone). Odśwież stronę, aby zobaczyć efekt.";
// Po usunięciu, ciasteczko nie będzie już dostępne w $_COOKIE przy następnym żądaniu.
// Aby natychmiast usunąć je z tablicy $_COOKIE w bieżącym skrypcie (tylko dla tego wykonania):
// if (isset($_COOKIE["uzytkownik"])) {
// unset($_COOKIE["uzytkownik"]);
// }
?>
Zalety i Wady Ciasteczek
Zalety:
- Proste w implementacji do przechowywania niewielkich ilości danych.
- Mogą być trwałe (przetrwać zamknięcie przeglądarki).
- Szeroko wspierane przez przeglądarki.
Wady:
- Ograniczony rozmiar: Zazwyczaj około 4KB na ciasteczko i limit liczby ciasteczek na domenę (ok. 20-50 w zależności od przeglądarki).
- Przechowywane po stronie klienta: Dane są widoczne dla użytkownika i mogą być przez niego modyfikowane lub usuwane. Nie należy przechowywać w nich wrażliwych informacji bez szyfrowania (a najlepiej w ogóle).
- Wysyłane z każdym żądaniem: Zwiększają rozmiar nagłówków HTTP, co może nieznacznie wpływać na wydajność, zwłaszcza przy dużej liczbie ciasteczek.
- Bezpieczeństwo: Podatne na kradzież (np. przez XSS, jeśli nie użyto
HttpOnly
) i inne ataki (np. CSRF, jeśli nie użytoSameSite
). - Prywatność: Śledzenie użytkowników za pomocą ciasteczek budzi obawy o prywatność (np. RODO/GDPR wymaga zgody na użycie niektórych typów ciasteczek).
Sesje (Sessions)
Sesje w PHP to mechanizm pozwalający na przechowywanie informacji o użytkowniku po stronie serwera przez cały czas trwania jego wizyty na stronie (lub dłużej). Zamiast przechowywać wszystkie dane u klienta (jak w ciasteczkach), serwer przechowuje tylko unikalny identyfikator sesji (Session ID) po stronie klienta, zazwyczaj w specjalnym ciasteczku sesyjnym. Dane sesji są przechowywane na serwerze, np. w plikach, w bazie danych lub w pamięci (np. Memcached, Redis).
Jak Działają Sesje?
- Rozpoczęcie sesji: Gdy sesja jest inicjowana (za pomocą
session_start()
), PHP sprawdza, czy klient przesłał identyfikator sesji (np. w ciasteczku). - Generowanie/Odtwarzanie ID Sesji:
- Jeśli klient nie przesłał ID sesji lub przesłane ID jest nieprawidłowe/wygasłe, PHP generuje nowy, unikalny identyfikator sesji.
- Jeśli klient przesłał prawidłowe ID sesji, PHP próbuje odtworzyć dane sesji powiązane z tym ID z magazynu po stronie serwera.
- Wysyłanie ID Sesji do Klienta: Nowo wygenerowany (lub istniejący) ID sesji jest wysyłany do przeglądarki klienta, najczęściej jako ciasteczko sesyjne (o nazwie zdefiniowanej w
session.name
, domyślniePHPSESSID
). Może być również przekazywany w URL (choć jest to mniej bezpieczne i niezalecane). - Przechowywanie i Odczyt Danych Sesji: Skrypt PHP może zapisywać i odczytywać dane w superglobalnej tablicy
$_SESSION
. - Zapis Danych Sesji: Po zakończeniu działania skryptu, PHP automatycznie zapisuje zawartość tablicy
$_SESSION
do skonfigurowanego magazynu sesji na serwerze. - Kolejne Żądania: Przy kolejnych żądaniach, klient odsyła ID sesji, a PHP używa go do odtworzenia odpowiednich danych sesji z serwera do tablicy
$_SESSION
.
Rozpoczynanie Sesji - Funkcja session_start()
Funkcja session_start()
inicjuje mechanizm sesji. Musi być wywołana na początku każdego skryptu, który ma korzystać z sesji, i przed jakimkolwiek innym wyjściem do przeglądarki (podobnie jak setcookie()
).
<?php
// session_start() musi być na samej górze, przed jakimkolwiek outputem
if (session_status() === PHP_SESSION_NONE) { // Sprawdzenie, czy sesja nie została już uruchomiona
session_start();
}
// Teraz można pracować z tablicą $_SESSION
echo "Sesja została uruchomiona. ID Sesji: " . session_id();
?>
Od PHP 5.4.0 session_status()
może być użyte do sprawdzenia statusu sesji: PHP_SESSION_DISABLED
, PHP_SESSION_NONE
(sesje włączone, ale żadna nieaktywna), PHP_SESSION_ACTIVE
.
Praca z Danymi Sesji - Superglobalna Tablica $_SESSION
Po wywołaniu session_start()
, można zapisywać i odczytywać dane w superglobalnej tablicy asocjacyjnej $_SESSION
.
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Zapisywanie danych do sesji
$_SESSION["username"] = "AlicjaWkrainiePHP";
$_SESSION["user_id"] = 123;
$_SESSION["licznik_odwiedzin"] = isset($_SESSION["licznik_odwiedzin"]) ? $_SESSION["licznik_odwiedzin"] + 1 : 1;
// Odczytywanie danych z sesji
if (isset($_SESSION["username"])) {
echo "Zalogowany użytkownik: " . htmlspecialchars($_SESSION["username"]) . "<br>";
}
echo "Odwiedziłeś tę stronę (w tej sesji) " . $_SESSION["licznik_odwiedzin"] . " raz(y).<br>";
// Wyświetlenie całej tablicy $_SESSION (do celów debugowania)
// echo "<h3>Zawartość ".htmlspecialchars("$_SESSION").":</h3><pre>";
// print_r($_SESSION);
// echo "</pre>";
?>
Kończenie Sesji i Usuwanie Danych
Aby usunąć poszczególne zmienne sesyjne, można użyć unset()
:
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Usunięcie konkretnej zmiennej sesyjnej
if (isset($_SESSION["user_id"])) {
unset($_SESSION["user_id"]);
}
?>
Aby usunąć wszystkie dane sesji (opróżnić tablicę $_SESSION
), można użyć session_unset()
:
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
session_unset(); // Usuwa wszystkie zmienne z $_SESSION
?>
Aby całkowicie zniszczyć sesję (usunąć dane sesji z serwera oraz unieważnić ID sesji), używa się session_destroy()
. Po jej wywołaniu, tablica $_SESSION
staje się pusta, a przy następnym żądaniu (jeśli sesja zostanie ponownie uruchomiona) zostanie wygenerowane nowe ID sesji.
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Najpierw usuń wszystkie zmienne sesyjne
session_unset();
// Następnie zniszcz sesję
if (session_destroy()) {
echo "Sesja została pomyślnie zniszczona.<br>";
} else {
echo "Nie udało się zniszczyć sesji.<br>";
}
// Dodatkowo, warto usunąć ciasteczko sesyjne po stronie klienta, aby zapewnić pełne "wylogowanie"
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(),
",
time() - 42000, // Czas w przeszłości
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]
);
echo "Ciasteczko sesyjne zostało usunięte (zlecone usunięcie).<br>";
}
// Po session_destroy(), $_SESSION jest pusta, a ID sesji jest unieważnione.
// Próba dostępu do $_SESSION["username"] teraz dałaby błąd (Undefined array key) lub null.
?>
Konfiguracja Sesji w PHP (php.ini
lub funkcje ini_set()
)
Zachowanie sesji można konfigurować za pomocą wielu dyrektyw w php.ini
lub dynamicznie za pomocą ini_set()
(przed wywołaniem session_start()
). Najważniejsze z nich to:
session.save_handler
: Określa, jak sesje są przechowywane (np.files
,user
,memcached
,redis
). Domyślniefiles
.session.save_path
: Ścieżka do katalogu, gdzie przechowywane są pliki sesji (jeślisession.save_handler
tofiles
). Proces PHP musi mieć uprawnienia do zapisu w tym katalogu.session.name
: Nazwa ciasteczka używanego do przechowywania ID sesji (domyślniePHPSESSID
).session.auto_start
: Czy sesja ma być automatycznie uruchamiana przy każdym żądaniu (domyślnie0
- wyłączone; zaleca się pozostawienie wyłączonego i ręczne wywoływaniesession_start()
).session.gc_probability
,session.gc_divisor
,session.gc_maxlifetime
: Kontrolują mechanizm "garbage collection" (zbierania śmieci), czyli usuwania starych, nieaktywnych plików sesji.gc_maxlifetime
to czas (w sekundach) nieaktywności sesji, po którym może zostać usunięta (domyślnie 1440, czyli 24 minuty).session.use_cookies
: Czy używać ciasteczek do przekazywania ID sesji (domyślnie1
- włączone; zalecane).session.use_only_cookies
: Czy ID sesji ma być przekazywane tylko za pomocą ciasteczek (domyślnie1
- włączone; zalecane, zapobiega przekazywaniu ID w URL, co jest mniej bezpieczne - session fixation).session.cookie_lifetime
: Czas życia ciasteczka sesyjnego (w sekundach). Domyślnie0
, co oznacza, że wygasa po zamknięciu przeglądarki.session.cookie_path
,session.cookie_domain
,session.cookie_secure
,session.cookie_httponly
,session.cookie_samesite
: Odpowiedniki parametrów funkcjisetcookie()
, ale dla ciasteczka sesyjnego. Bardzo ważne jest ustawieniesession.cookie_httponly = On
isession.cookie_secure = On
(dla HTTPS) oraz rozważeniesession.cookie_samesite
.session.use_strict_mode
(od PHP 5.5.2): Jeśli włączone (1
), moduł sesji nie akceptuje niezainicjowanych ID sesji. Generuje nowe ID, jeśli klient prześle nieistniejące. Pomaga chronić przed session fixation. Zalecane.session.sid_length
(od PHP 7.1): Długość generowanego ID sesji. Domyślnie 32 znaki.session.sid_bits_per_character
(od PHP 7.1): Liczba bitów na znak w ID sesji (np. 4 dla hex, 5 dla base32, 6 dla base64).
Przykład zmiany konfiguracji przed session_start()
:
<?php
ini_set("session.cookie_httponly", 1);
ini_set("session.use_strict_mode", 1);
ini_set("session.gc_maxlifetime", 1800); // Sesja wygasa po 30 minutach nieaktywności
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// ... reszta kodu sesji ...
?>
Bezpieczeństwo Sesji
Bezpieczeństwo sesji jest kluczowe, zwłaszcza w systemach logowania i aplikacjach przetwarzających wrażliwe dane.
- Regeneracja ID Sesji (
session_regenerate_id()
): Aby zapobiec atakom typu Session Fixation (ustalenie sesji), należy regenerować ID sesji po każdej istotnej zmianie stanu, np. po zalogowaniu użytkownika lub zmianie poziomu uprawnień. Funkcjasession_regenerate_id(true)
generuje nowe ID i usuwa stary plik sesji. - Używaj HTTPS: Zawsze używaj HTTPS, aby szyfrować komunikację między klientem a serwerem, w tym przesyłanie ID sesji w ciasteczku. Ustaw
session.cookie_secure = On
. HttpOnly
iSecure
dla Ciasteczek Sesyjnych: Ustawsession.cookie_httponly = On
isession.cookie_secure = On
.SameSite
dla Ciasteczek Sesyjnych: Rozważ ustawieniesession.cookie_samesite
na"Lax"
lub"Strict"
.session.use_strict_mode = On
: Zwiększa bezpieczeństwo przed session fixation.- Nie Przechowuj Wrażliwych Danych Bezpośrednio w
$_SESSION
, jeśli nie jest to konieczne: Jeśli przechowujesz np. ID użytkownika, to wystarczy. Unikaj przechowywania haseł (nawet zahashowanych) czy numerów kart kredytowych w sesji. - Waliduj Dane Użytkownika w Sesji: Nie ufaj ślepo danym odczytanym z sesji, zwłaszcza jeśli mogły zostać ustawione na podstawie wcześniejszego, potencjalnie zmodyfikowanego, wejścia użytkownika.
- Ustawiaj Odpowiedni Czas Życia Sesji (
session.gc_maxlifetime
): Zbyt długi czas życia nieaktywnej sesji zwiększa ryzyko jej przejęcia. - Zapewnij Mechanizm Wylogowania: Zawsze implementuj funkcję wylogowania, która niszczy sesję (
session_unset()
,session_destroy()
) i usuwa ciasteczko sesyjne. - Ochrona przed Session Hijacking (Przejęciem Sesji): Dodatkowe techniki mogą obejmować sprawdzanie User-Agenta przeglądarki lub adresu IP powiązanego z sesją (choć to może być problematyczne dla użytkowników z dynamicznym IP lub za proxy).
<?php
// Po pomyślnym zalogowaniu użytkownika:
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// ... logika weryfikacji loginu i hasła ...
if ($uzytkownik_poprawnie_zalogowany) {
session_regenerate_id(true); // Regeneruj ID sesji, usuwając stare
$_SESSION["zalogowany"] = true;
$_SESSION["user_id"] = $id_pobrany_z_bazy;
}
?>
Zalety i Wady Sesji
Zalety:
- Dane przechowywane na serwerze: Bezpieczniejsze niż ciasteczka, ponieważ dane nie są bezpośrednio dostępne dla klienta. Klient przechowuje tylko ID sesji.
- Większa pojemność: Można przechowywać znacznie więcej danych niż w ciasteczkach (limit zależy od konfiguracji serwera i dostępnej pamięci/przestrzeni dyskowej).
- Łatwiejsze zarządzanie złożonymi danymi: Można przechowywać tablice i obiekty (PHP automatycznie je serializuje/deserializuje).
Wady:
- Obciążenie serwera: Przechowywanie i zarządzanie danymi sesji na serwerze zużywa zasoby (pamięć, dysk, procesor).
- Skalowalność: W środowiskach rozproszonych (wiele serwerów) zarządzanie sesjami może być bardziej skomplikowane (wymaga współdzielonego magazynu sesji, np. bazy danych, Memcached, Redis, lub "sticky sessions" na load balancerze).
- Zależność od ID Sesji: Jeśli ID sesji zostanie przejęte, atakujący może uzyskać dostęp do sesji użytkownika.
Ciasteczka vs. Sesje - Kiedy Czego Używać?
Cecha | Ciasteczka (Cookies) | Sesje (Sessions) |
---|---|---|
Przechowywanie danych | Po stronie klienta (w przeglądarce) | Po stronie serwera |
Przesyłane dane | Cała zawartość ciasteczka z każdym żądaniem | Tylko ID sesji (zazwyczaj w ciasteczku) |
Rozmiar danych | Mały (ok. 4KB na ciasteczko, limit liczby) | Duży (ograniczony zasobami serwera) |
Bezpieczeństwo danych | Niższe (widoczne i modyfikowalne przez klienta) | Wyższe (dane na serwerze, klient ma tylko ID) |
Trwałość | Mogą być trwałe (ustawiony czas wygaśnięcia) lub sesyjne | Zazwyczaj trwają do zamknięcia przeglądarki lub wygaśnięcia na serwerze (gc_maxlifetime ) |
Zastosowania | Zapamiętywanie preferencji (np. motyw strony), śledzenie (za zgodą), małe, niewrażliwe dane | Logowanie użytkowników, koszyki zakupowe, przechowywanie stanu aplikacji, wrażliwe dane tymczasowe |
Zależność | Użytkownik może wyłączyć ciasteczka | Zazwyczaj polegają na ciasteczkach do przechowywania ID sesji (choć możliwe jest przekazywanie ID w URL) |
Generalna zasada:
- Używaj ciasteczek do przechowywania niewielkich, niewrażliwych informacji, które mają być dostępne przez dłuższy czas lub które dotyczą preferencji użytkownika (np. język strony, zgoda na RODO).
- Używaj sesji do przechowywania wrażliwych danych, informacji o zalogowanym użytkowniku, stanu aplikacji (np. zawartość koszyka) oraz większych ilości danych, które są potrzebne tylko podczas aktywnej wizyty użytkownika.
Podsumowanie Lekcji
W tej lekcji nauczyliśmy się, jak zarządzać stanem użytkownika w PHP za pomocą ciasteczek i sesji. Omówiliśmy sposób działania obu mechanizmów, funkcje PHP służące do ich obsługi (setcookie()
, $_COOKIE
, session_start()
, $_SESSION
, session_destroy()
itp.), a także kluczowe aspekty konfiguracji i bezpieczeństwa. Zrozumienie różnic między ciasteczkami a sesjami oraz umiejętność ich właściwego stosowania jest fundamentalne dla tworzenia dynamicznych i bezpiecznych aplikacji internetowych.
Pamiętaj, że bezpieczeństwo danych użytkownika i jego sesji powinno być zawsze priorytetem. W następnej lekcji zajmiemy się pracą z datą i czasem w PHP.
Zadanie praktyczne
Stwórz prosty system logowania i wylogowywania użytkownika oparty na sesjach.
- Utwórz plik
login_form.html
z formularzem zawierającym pola: "Login" (username
) i "Hasło" (password
). Formularz powinien wysyłać dane metodą POST do skryptulogin_handler.php
. - W skrypcie
login_handler.php
:- Rozpocznij sesję (
session_start()
). - Odbierz dane z formularza.
- Dla uproszczenia, załóżmy, że poprawny login to "admin", a hasło to "secret123". W rzeczywistej aplikacji hasła byłyby hashowane i sprawdzane z bazą danych.
- Jeśli dane logowania są poprawne: zregeneruj ID sesji (
session_regenerate_id(true)
), zapisz w$_SESSION
informację o zalogowanym użytkowniku (np.$_SESSION["user"] = "admin"; $_SESSION["logged_in"] = true;
) i przekieruj użytkownika na stronępanel_uzytkownika.php
. - Jeśli dane są niepoprawne, wyświetl komunikat o błędzie i link powrotny do formularza logowania.
- Rozpocznij sesję (
- Utwórz plik
panel_uzytkownika.php
:- Rozpocznij sesję.
- Sprawdź, czy użytkownik jest zalogowany (czy istnieje odpowiednia zmienna w
$_SESSION
). - Jeśli jest zalogowany, wyświetl powitanie (np. "Witaj, [nazwa_uzytkownika]!") oraz link/przycisk "Wyloguj" prowadzący do
logout.php
. - Jeśli nie jest zalogowany, wyświetl komunikat "Dostęp tylko dla zalogowanych użytkowników" i przekieruj na
login_form.html
po kilku sekundach (lub od razu).
- Utwórz plik
logout.php
:- Rozpocznij sesję.
- Usuń wszystkie dane sesji (
session_unset()
). - Zniszcz sesję (
session_destroy()
). - Usuń ciasteczko sesyjne.
- Przekieruj użytkownika na
login_form.html
z komunikatem o pomyślnym wylogowaniu.
- Na każdej stronie (
login_handler.php
,panel_uzytkownika.php
,logout.php
) pamiętaj o wywołaniusession_start()
na samym początku. Zadbaj o bezpieczeństwo ciasteczka sesyjnego (HttpOnly, Secure jeśli testujesz na HTTPS, SameSite).
Pokaż przykładowe rozwiązanie
login_form.html
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Logowanie</title>
</head>
<body>
<h2>Zaloguj się</h2>
<form action="login_handler.php" method="POST">
<p><label for="username">Login:</label> <input type="text" id="username" name="username" required></p>
<p><label for="password">Hasło:</label> <input type="password" id="password" name="password" required></p>
<p><input type="submit" value="Zaloguj"></p>
</form>
<?php if(isset($_GET["error"])) echo "<p style=\"color:red;\">Błędny login lub hasło.</p>"; ?>
<?php if(isset($_GET["logout"])) echo "<p style=\"color:green;\">Pomyślnie wylogowano.</p>"; ?>
</body>
</html>
login_handler.php
<?php
ini_set("session.cookie_httponly", 1);
ini_set("session.use_strict_mode", 1);
// ini_set("session.cookie_secure", 1); // Odkomentuj, jeśli testujesz na HTTPS
ini_set("session.cookie_samesite", "Lax");
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$poprawny_login = "admin";
$poprawne_haslo = "secret123"; // W praktyce hashowane hasło!
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$login = isset($_POST["username"]) ? trim($_POST["username"]) : "";
$haslo = isset($_POST["password"]) ? $_POST["password"] : "";
// W praktyce tutaj byłoby porównanie z zahashowanym hasłem z bazy
if ($login === $poprawny_login && $haslo === $poprawne_haslo) {
session_regenerate_id(true); // Kluczowe dla bezpieczeństwa!
$_SESSION["user"] = $login;
$_SESSION["logged_in"] = true;
header("Location: panel_uzytkownika.php");
exit;
} else {
header("Location: login_form.html?error=1");
exit;
}
} else {
header("Location: login_form.html");
exit;
}
?>
panel_uzytkownika.php
<?php
ini_set("session.cookie_httponly", 1);
ini_set("session.use_strict_mode", 1);
// ini_set("session.cookie_secure", 1);
ini_set("session.cookie_samesite", "Lax");
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (isset($_SESSION["logged_in"]) && $_SESSION["logged_in"] === true && isset($_SESSION["user"])) {
$username = htmlspecialchars($_SESSION["user"]);
echo "<!DOCTYPE html><html lang=\"pl\"><head><meta charset=\"UTF-8\"><title>Panel Użytkownika</title></head><body>";
echo "<h1>Witaj w panelu, $username!</h1>";
echo "<p>To jest Twoja prywatna strefa.</p>";
echo "<p><a href=\"logout.php\">Wyloguj</a></p>";
echo "</body></html>";
} else {
// Użytkownik nie jest zalogowany
echo "<!DOCTYPE html><html lang=\"pl\"><head><meta charset=\"UTF-8\"><title>Brak dostępu</title>";
// echo "<meta http-equiv=\"refresh\" content=\"3;url=login_form.html\">"; // Opcjonalne przekierowanie po 3s
echo "</head><body>";
echo "<h1 style=\"color:red;\">Brak dostępu!</h1>";
echo "<p>Dostęp do tej strony mają tylko zalogowani użytkownicy.</p>";
echo "<p>Zostaniesz przekierowany na stronę logowania... lub <a href=\"login_form.html\">kliknij tutaj</a>.</p>";
echo "</body></html>";
// Dla natychmiastowego przekierowania:
// header("Location: login_form.html");
// exit;
}
?>
logout.php
<?php
ini_set("session.cookie_httponly", 1);
ini_set("session.use_strict_mode", 1);
// ini_set("session.cookie_secure", 1);
ini_set("session.cookie_samesite", "Lax");
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// 1. Usuń wszystkie zmienne sesyjne
$_SESSION = array(); // lub session_unset();
// 2. Zniszcz sesję
if (session_destroy()) {
// echo "Sesja zniszczona.<br>"; // Debug
}
// 3. Usuń ciasteczko sesyjne
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(),
",
time() - 42000,
$params["path"],
$params["domain"],
$params["secure"],
$params["httponly"]
);
// echo "Ciasteczko sesyjne usunięte.<br>"; // Debug
}
// 4. Przekieruj na stronę logowania z komunikatem
header("Location: login_form.html?logout=1");
exit;
?>
Zadanie do samodzielnego wykonania
Rozbuduj powyższy system logowania o następujące elementy:
- Dodaj licznik nieudanych prób logowania. Jeśli użytkownik pomyli się 3 razy podając hasło dla danego loginu, zablokuj możliwość logowania na ten login na np. 5 minut. Informacje o liczbie prób i czasie blokady przechowuj w sesji lub w tymczasowym pliku/prostej bazie danych (dla uproszczenia możesz użyć sesji, ale pamiętaj, że sesja jest per użytkownik, więc blokada będzie działać tylko dla tej samej przeglądarki/sesji).
- Na stronie
panel_uzytkownika.php
dodaj formularz pozwalający użytkownikowi ustawić kolor tła strony (np. wybór z listy: jasny, ciemny, niebieski). Zapisz ten wybór w ciasteczku (trwałym, np. na 30 dni) i przy każdym załadowaniu stronypanel_uzytkownika.php
odczytuj wartość z ciasteczka i dynamicznie ustawiaj styl tła strony.
FAQ - Sesje i Ciasteczka
Czy session_start()
zawsze musi być na początku skryptu?
Tak, session_start()
musi być wywołane przed jakimkolwiek wysłaniem treści do przeglądarki (HTML, spacje, echo). Dzieje się tak, ponieważ inicjalizacja sesji może wymagać wysłania nagłówków HTTP (np. ciasteczka sesyjnego), a nagłówki muszą być wysłane przed treścią odpowiedzi.
Jak przechowywać obiekty w sesji?
PHP automatycznie serializuje (zamienia na string) obiekty i tablice przed zapisaniem ich do $_SESSION
i deserializuje (odwzorowuje z powrotem na obiekt/tablicę) przy odczycie. Aby to działało poprawnie, definicja klasy obiektu musi być dostępna (załadowana) przed wywołaniem session_start()
lub przed pierwszym dostępem do obiektu z sesji.
Co to jest Session Fixation i jak się przed nim chronić?
Session Fixation to atak, w którym atakujący ustala ID sesji ofiary przed jej zalogowaniem. Gdy ofiara się zaloguje używając tego ID, atakujący może przejąć jej sesję. Główną metodą ochrony jest regeneracja ID sesji (session_regenerate_id(true)
) natychmiast po każdej zmianie poziomu uwierzytelnienia (np. po zalogowaniu).
Czy mogę używać sesji bez ciasteczek?
Tak, PHP pozwala na przekazywanie ID sesji w URL (jeśli session.use_cookies=0
i session.use_trans_sid=1
), ale jest to zdecydowanie niezalecane ze względów bezpieczeństwa (ID sesji jest widoczne, może być logowane, udostępniane). Zawsze preferuj użycie ciasteczek z odpowiednimi flagami bezpieczeństwa.
Jak długo trwa sesja PHP?
Domyślnie ciasteczko sesyjne wygasa po zamknięciu przeglądarki. Jednak dane sesji na serwerze są usuwane przez mechanizm garbage collection po czasie nieaktywności określonym w session.gc_maxlifetime
(domyślnie 24 minuty). Czas życia sesji można kontrolować tymi ustawieniami oraz session.cookie_lifetime
.
Gdzie są przechowywane pliki sesji na serwerze?
Jeśli używany jest domyślny handler files
, ścieżka do katalogu z plikami sesji jest określona w dyrektywie session.save_path
w php.ini
. Każda sesja to osobny plik, zwykle o nazwie sess_<ID_SESJI>
.
Czy mogę przechowywać w ciasteczkach dane wrażliwe?
Zdecydowanie nie zaleca się przechowywania danych wrażliwych (haseł, danych osobowych, numerów kart) w ciasteczkach, ponieważ są one przechowywane po stronie klienta i mogą być odczytane lub zmodyfikowane. Jeśli musisz, dane powinny być silnie zaszyfrowane, ale sesje są znacznie lepszym miejscem na takie informacje.
Co oznacza atrybut SameSite
dla ciasteczek?
Atrybut SameSite
(Strict
, Lax
, None
) kontroluje, czy ciasteczko jest wysyłane z żądaniami cross-site (z innej domeny). Pomaga to chronić przed atakami CSRF. Lax
jest dobrym kompromisem, a Strict
zapewnia najsilniejszą ochronę, ale może wpływać na niektóre funkcjonalności. None
wymaga flagi Secure
.