HTTP je bezestavový protokol, nicméně takřka každá aplikace potřebuje stav mezi požadavky uchovávat, například obsah
nákupního košíku. Právě k tomu slouží session neboli relace. Ukážeme si,
- jak používat sessions
- jak předejít jmenným konfliktům
- nastavení expirace
- zabezpečení proti zranitelnostem
Při použití sessions každý uživatel, který vstoupí na stránku, obdrží jedinečný identifikátor Session ID, který
se předává v cookies. Ten slouží jako klíč k session datům. Na rozdíl od cookies, které se uchovávají na straně
prohlížeče, jsou data v session uchovávána na straně serveru.
Správu session má na starosti objekt Nette\Http\Session, ke
kterému se dostanete tak, že si jej necháte předat pomocí dependency injection.
V presenterech stačí jen zavolat $session = $this->getSession()
.
Sekce
V čistém PHP je datové úložiště session realizováno jako pole dostupné přes globální proměnnou
$_SESSION
. Problém je v tom, že aplikace se běžně skládají z celé řady vzájemně nezávislých částí a
pokud všechny mají k dispozici jen jedno pole, dříve nebo později dojde ke kolizi názvů.
Nette Framework problém řeší tak, že celý prostor rozděluje na sekce (objekty Nette\Http\SessionSection). Každá část programu pak
používá svou sekci s unikátním názvem a k žádné kolizi již dojít nemůže.
Začneme od správce session, což je objekt třídy Nette\Http\Session. Pokud pracujeme z presenteru, můžeme použít
doporučovanou zkratku:
// $this je Presenter
$session = $this->getSession(); // returns the whole Session
$mySection = $this->getSession('mySection'); // returns SessionSection with given name
Pokud pořebujete se Session pracovat mimo presenter, můžete si ji vyžádat pomocí dependency
injection, v našem případě pomocí konstruktoru:
use Nette;
class MyService
{
/** @var Nette\Http\Session */
private $session;
/** @var Nette\Http\SessionSection */
private $sessionSection;
public function __construct(Nette\Http\Session $session)
{
$this->session = $session;
// a získáme přístup do sekce 'mySection':
$this->sessionSection = $session->getSection('mySection');
}
}
Ověřit existenci sekce lze metodou hasSection('myCounter')
.
Session přitom není potřeba startovat nebo uzavírat, tohle provádí framework automaticky. Můžeme ji však nastartovat
i manuálně voláním $session->start()
. Pokud zavoláme metodu start() vícekrát, nic se nestane a nebude to
mít žádný efekt. Startování můžete také nastavit v konfiguraci
Se samotnou sekcí se pak pracuje velmi snadno:
// zápis proměnné
$section->userName = 'franta'; // nebo $section['userName'] = 'franta';
// čtení proměnné
echo $section->userName; // nebo echo $section['userName'];
// zrušení proměnné
unset($section->userName); // unset($section['userName']);
Pro získání všech proměnných ze sekce je možné použít cyklus foreach
:
foreach ($section as $key => $val) {
echo "$key = $val";
}
Příklad: čítač přístupů
Začněme příkladem počítadla, které ukazuje, kolikrát uživatel zobrazil stránku.
$myCounter = $session->getSection('myCounter');
// nebo také $this->getSession('myCounter'); kde $this je presenter
$myCounter->count++; // zvětšíme čítač o jedničku
echo "Stránku jste zobrazil $myCounter->count ×";
Přístup k neexistující proměnné negeneruje žádnou chybu (proměnná má hodnotu NULL). To však může být
v určitých případech nežádoucí, proto existuje možnost, jak chování pro konkrétní sekci změnit:
$myCounter->warnOnUndefined = TRUE;
Nastavení expirace
Velmi užitečnou vlastností je možnost nastavit vlastní expiraci pro jednotlivé sekce nebo dokonce pro jednotlivé
proměnné. Můžeme tak nechat vypršet přihlášení uživatele při zavření prohlížeče, ale přitom si nadále pamatovat
obsah košíku.
// sekce vyexpiruje po 2 minutách
$section->setExpiration('2 minutes');
// a proměnná $section->a vyexpiruje už po 10 sekundách
$section->setExpiration(10, 'a');
Kromě relativního času v sekundách lze použít UNIX timestamp nebo textový zápis. Zajímavostí je hodnota
0
, který nastaví expiraci na okamžik, kdy uživatel zavře prohlížeč:
// proměnná $section->password vyexpiruje při zavření prohlížeče
$section->setExpiration(0, 'password');
Nezapomeňte, že doba expirace celé session (viz Konfigurace session)
musí být stejná nebo vyšší, než doba nastavená u jednotlivých sekcí či proměnných.
Zrušení dříve nastavené expirace docílíme metodou removeExpiration()
. Okamžité zrušení celé sekce
zajistí metoda remove()
.
Konfigurace session
Konfigurace session musí být provedena dříve, než se začnou session používat. V aplikacích je nejvhodnější ji
umístit do konfiguračního souboru config.neon
.
Co konfigurovat? Především je to expirace. Pokud se neprovede toto nastavení, všechny session proměnné vyexpirují
v momentě zavření okna prohlížeče. Uchování session i po zavření prohlížeče se hodí například pro dlouhodobé
přihlášení uživatele.
session:
expiration: 14 days
Můžete také konfigurovat automatické startování session:
session:
autoStart: true # výchozí hodnota je 'smart'
Doporučuje se používat autoStart: smart
, protože pak automaticky startuje session, pouze pokud je
již vytvořena.
Na sdílených hostinzích je vhodné zvolit vlastní adresář, kam se mají ukládat soubory s relacemi:
session:
savePath: "%tempDir%/sessions"
V tomto příkladě se %tempDir%
nahradí hodnotou, kterou jste nastavili v
$configurator->setTempDirectory()
v bootstrap.php
.
Pokud chceme platnost session (nebo autentizace) rozšířit na subdomémy, nastavíme ještě parametry cookie:
// nastaví platnost na všechny subdomény
session:
cookiePath: '/'
cookieDomain: '.example.com'
Bezpečnost především
Server předpokládá, že komunikuje stále s tímtéž uživatelem, dokud požadavky doprovází stejné Session ID. Úkolem
bezpečnostních mechanismů je zajistit, aby tomu tak doopravdy bylo a nebylo možné identifikátor odcizit nebo podstrčit.
Nette Framework proto správně nakonfiguruje PHP direktivy, aby Session ID přenášel pouze v cookie, znepřístupnil jej
JavaScriptu a případné identifikátory v URL ignoroval. Navíc v kritických chvílích, jako je třeba přihlášení
uživatele, vygeneruje Session ID nové.
Pro konfiguraci PHP se používá funkce ini_set, kterou bohužel některé hostingy zakazují. Pokud je to
případ i vašeho hostéra, pokuste se s ním domluvit, aby vám funkci povolil nebo alespoň server nakonfiguroval.
Známá omezení
V prohlížeči Chrome je v nastavení volba Po ukončení prohlížeče Google Chrome nechat aplikace na pozadí
spuštěné, která je ve výchozím stavu zapnutá. Způsobuje, že cookie, která má nastavenou expiraci do uzavření
prohlížeče, se nesmaže. Sekce s expirací 0 se tedy po zavření prohlížeče nesmaže.