Sessions
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.
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.
Jelikož jde o singleton, nevytváříme jeho instanci přímo, ale získáme
ji jako službu session ze systémového DI kontejneru.
// $container je systémový kontejner
$session = $container->getService('session');
// nebo zkráceně:
$session = $container->session;
// v novém containeru je třeba samostatně startovat, jelikož není zapnutý autostart
if ($container->session->exists()) {
$session = $container->session->start();
}
// a získáme přístup do sekce 'myCounter':
$section = $session->getSection('myCounter');
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.
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.
$section = $container->session->getSection('myCounter');
$section->count++; // zvětšíme čítač o jedničku
echo "Stránku jste zobrazil $section->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:
$section->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
bootstrap.php.
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.
$container->session->setExpiration('+ 14 days');
Na sdílených hostinzích je vhodné zvolit vlastní adresář, kam se mají ukládat soubory s relacemi:
$session->setSavePath(__DIR__ . '/sessions');
Pokud chceme platnost session (nebo autentizace) rozšířit na subdomémy, nastavíme ještě parametry cookie:
// nastaví platnost na všechny subdomény
$container->session->setCookieParams('/', '.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 Sesssion 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. Jinak bude třeba bezpečnostní mechanismy vypnout
pomocí Nette\Framework::$iAmUsingBadHost = TRUE
Komentáře 
mr.mac | 16. 10. 2011, 11:47 | question
Je možné popsat kam inicializaci globálních proměnných, které chci uchovat v SESSION umístit a jakým způsobem je z jiných modulů aktualizovat (asi v Prezenterech příslušných modulů/akcí) v Nette 2.0 (díky Namespaces se v tom mám hokej)? Díky.
bojovyletoun | 29. 1. 2012, 18:41 | question
Ahoj, všiml jsem nejednoznačnosti u přístupu k proměnným u sessionSection:
$section = $this->session->getSection('data2');
$item = $section->someArray;
$iten = & $section->someArray; // ok
$item = $section['someArray'];
$item = & $section['someArray']; // nee (vyhodí strict)
&__get($n) totiž vrací referenci, kdežto offsetGet($n) ne. To je schválně?


bojovyletoun | 20. 9. 2011, 13:04 | comment
ahoj, je oštřeno session stealing ( → kontrola IP)