Nette Framework: Sessions

Pokud chcete naprogramovat šikovnější webovou aplikaci, neobejdete se bez sessions. Ukážeme si, jak Nette Framework zjednoduší práci se sessions a jak řeší s tím spojená bezpečnostní rizika.
Seriál: Začínáme s Nette Framework (17 dílů)
- Nette Framework: zvyšte svoji produktivitu 10. 3. 2009
- Nette Framework: Odvšivujeme 17. 3. 2009
- Nette Framework: MVC & MVP 24. 3. 2009
- Nette Framework: Refactoring 31. 3. 2009
- Nette Framework: Chytré šablony 7. 4. 2009
- Nette Framework: adresářová struktura aplikace 14. 4. 2009
- Nette Framework: AJAX 21. 4. 2009
- Nette Framework: AJAX (pokračování) 28. 4. 2009
- Nette Framework: AJAX (dokončení) 5. 5. 2009
- Nette Framework: Sessions 12. 5. 2009
- Nette Framework: Přihlašování uživatelů 19. 5. 2009
- Nette Framework: Ověřování oprávnění a role 26. 5. 2009
- Nette Framework: Neprůstřelné formuláře 2. 6. 2009
- Nette Framework: Neprůstřelné formuláře II 9. 6. 2009
- Nette Framework: Neprůstřelné formuláře III 16. 6. 2009
- Nette Framework: Cache 23. 6. 2009
- Nette Framework: Co se do seriálu nevešlo? 30. 6. 2009
Nálepky:
Protokol HTTP je bezestavový. Což znamená, že každý dotaz na server je nezávislý a že si server nepamatuje stav komunikace. Takto by však nebylo možné implementovat žádnou jen trošku složitější aplikaci, např. internetový obchod, který si potřebuje uchovávat obsah nákupního košíku. Proto byl protokol HTTP rozšířen o tzv. HTTP cookies, krátké textové řetězce, které umožňují informace o stavu uchovávat.
Chcete se naučit o Nette víc?
Akademie Root.cz školení Vývoj webových aplikací v Nette Framework. Kurz je určen všem programátorům v PHP, kteří se chtějí naučit tvořit webové aplikace rychle a kvalitně, bez bezpečnostních děr. Jako aplikační rámec slouží Nette Framework. Školí sám autor Nette – David Grudl. Máte zájem o jiné školení? Napište nám!
Jelikož tyto řetězce jsou skutečně krátké a ukládají se na straně prohlížeče, nemusí být vždy možné nebo vhodné do nich celý stav zapisovat. Řešením jsou tzv. sessions (česky relace, sezení). Princip je ten, že informace o stavu se uchovávají na straně serveru, kde jsou dostupné pod jedinečným identifikátorem zvaným Session ID a ten jediný stačí uložit do HTTP cookie.
Bezpečnost především
Server tedy 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.
Když se někdy v devadesátých letech začalo diskutovat o bezpečnosti webových aplikací, vznikl mýtus, že cookies jsou samy o sobě nebezpečné a rádobyodborníci nabádali veřejnost, aby si je vypínala. Což samozřejmě ochromilo celou řadu webových aplikací. Aby mohly fungovat i bez cookies, začalo se Session ID přenášet také v URL. Což teprve zavdalo vzniku těch největších bezpečnostních děr. Obrácená kauzalita.
Dnes už je situace zcela jiná. Uživatelé si cookies nevypínají a bezpečnost aplikací má vysokou prioritu. Velmi důležité je tedy nakonfigurovat server tak, aby Session ID přenášel pouze v cookie, znepřístupnil jej JavaScriptu a případné identifikátory v URL ignoroval.
Nette Framework na scénu!
Jak už zaznělo v předcházejících dílech seriálu, Nette Framework klade velký důraz na bezpečnost aplikací. Připomeňme třeba šablonovací systém, který má ambici zcela eliminovat Cross Site Scripting. Nepřekvapí proto, že framework se snaží co nejlépe zabezpečit i sessions. Dělá to zcela transparentně, aniž byste museli cokoliv manuálně nastavovat.
Nette Framework tedy sám správně nakonfiguruje PHP direktivy*), kontroluje neměnost vybraných HTTP hlaviček zasílaných prohlížečem, v kritických okamžicích, jako je třeba přihlášení uživatele, vygeneruje Sesssion ID nové atd.
*) pro konfiguraci PHP se používá funkce ini_set, kterou bohužel některé hostingy nepovolují. 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. Nevyhoví-li, vůbec neváhejte a hostéra změňte. Ušetříte si tak spoustu problémů.
Přístup k sessions obstarává objekt třídy NetteWebSession
. Jelikož jde o singleton, nevytváříme jeho instanci přímo, ale vrátí ji metoda Environment::getSession()
. Poté můžeme dokončit konfiguraci. Například takto lze nastavit dobu expirace, zvolit adresář pro soubory se stavem relací a upřesnit parametry cookie:
require 'Nette/loader.php';
$session = Environment::getSession();
// nastavení expirace: relace vyprší po 3 hodinách neaktivity
$session->setExpiration(3 * 60 * 60);
// nastavení cesty, kam se mají ukládat soubory na serveru
$session->setSavePath(dirname(__FILE__) . '/sessions/');
// nastavení parametrů cookie: bude dostupné jen na doméně forum.example.com
$session->setCookieParams('/', 'forum.example.com');
Konfigurace musí být provedena dříve, než se začnou session data používat. V aplikacích je nejvhodnější ji umístit do bootstrap.php
.
Ještě se zastavím u volby doby expirace. Výchozí hodnota „do zavření okna prohlížeče“ nemusí být vždy optimální. Metoda setExpiration()
jako parametr akceptuje relativní čas v sekundách nebo UNIX timestamp, v aktuální verzi frameworku je možné použít i velmi srozumitelný textový zápis:
// relace vyprší po 14 dnech neaktivity
$session->setExpiration('+ 14 days');
Session není potřeba startovat nebo uzavírat, tohle provádí framework automaticky.
Jmenné prostory
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ádá 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 jmenné prostory. Každá část programu pak používá svůj jmenný prostor (s unikátním názvem) a k žádné kolizi již dojít nemůže. Se jmenným prostorem se pracuje podobně, jako by šlo o objekt:
// získáme přístup do jmenného prostoru 'myCounter'
$namespace = $session->getNamespace('myCounter');
// nastavíme proměnnou
$namespace->a = 'apple';
// lze použít i syntax: $namespace['a'] = 'apple';
// přečteme proměnnou
echo $namespace->a;
// zrušíme proměnnou
unset($namespace->a);
Nette nabízí zkratku, místo Environment::getSession()->getNamespace('prostor')
lze psát Environment::getSession('prostor')
.
Velmi užitečnou vlastností je možnost nastavit vlastní expiraci pro jednotlivé jmenné prostory nebo dokonce pro jednotlivé proměnné:
// jmenný prostor vyexpiruje po 60 sekundách
$namespace->setExpiration(60);
// a proměnná $namespace->a vyexpiruje už po 10 sekundách
$namespace->setExpiration(10, 'a');
Opět je možné kromě relativního času v sekundách použít UNIX timestamp nebo textový zápis. Zajímavostí je hodnota 0
, který nastaví expiraci na okamžik, kdy uživatel zavře okno prohlížeče:
// proměnná $namespace->password vyexpiruje, jakmile uživatel zavře okno prohlížeče
$namespace->setExpiration(0, 'password');
Nezapomeňte, že doba expirace celé session musí být stejná nebo větší, než doba nastavená u jednotlivých prostorů či proměnných.
Pokračování příště
Příště se podíváme na přihlašování uživatelů.
Autor článku je vývojář na volné noze, specializuje se na návrh a programování moderních webových aplikací. Vyvíjí open-source knihovny Texy, dibi a Nette Framework a pravidelně pořádá školení pro tvůrce webových aplikací, které od podzimu 2009 nabídne kurz vývoje AJAXových aplikací.
Krásné zjednodušení oficiálního API! Něco na tom Nette bude :)
$namespace->a = 'apple';
…magic methods jsou tou nejlepsi cestou k write-only kodu :/… pokud se používají nerozumně. Naopak, při rozumném použití to zvyšuje čitelnost. Nejsem pro kratší kód za každou cenu, ale proč psát $x->getElement('foo'), pokud jde o nějakou podobu mapy a tudíž zápis $x['foo'] vypadá logicky?
A proč tedy nepoužít přímo pole?
.. aplikace se běžně skládá 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ů ..
Co třeba
$_SESSION['module']['variable']
Obecně se může hodit nějaká kontrola, dál pole nemá Observery apod.
Což ale nefunguje, jak praxe prokázala. Proto je v Nette potřeba přistupovat přes nějaký jmenný prostor a neexistuje ani žádný "výchozí" jmenný prostor.
Nějaký odkaz na tu praxi, která něco prokázala, by nebyl? Já totiž takový problém nezažil … díky.
Například tohle http://jdem.cz/beud6
No protoze na prvni pohled a bez studovani ejakeho kontextu pak neni poznat, jestli je $x pole nebo objekt a to prodluzuje cas, ktery je potreba na proniknuti do programu u cloveka, ktery kod vidi poprve (nebo po sto letech svuj vlastni ;-) )
V třídy Environment si snad vždy vzpomenu na tvůj odpor k Singletonu a moji obhajobu (za určitých podmínek!): http://phpfashion.com/je-singleton-zlo#comment-13682
Když bych to řekl ošklivě: singletony používají ti, kteří se ještě nenaučili psát bez nich
:-)
Proti singletonu realizovanému pomocí factory method nic nemám, vadí mi způsob popisovaný na stránce Wikipedia http://en.wikipedia.org/wiki/Singleton_pattern jako "tradiční".
Šlo mi hlavně o to, že Environment třeba není neměnná třída.
Jinak musím upřesnit, že nelze obecně říct, že schvaluji každý Factory-Singleton pro neměnnou třídu. Uvedený případ s Clock bych dnes už považoval za hraniční – záleží na míře použití Dependency Injection. Environment by mi vadil už tehdy.
Vzhledem k tomu, ze to nepodporuje ukladani sessions do databaze, ale pouze do souboru, tak jako standardni PHP session handler.
Podporuje, muzete si nastavit jakykoliv handler.
Jak se zachova Session pri soubeznem zapisu, napr ze dvou AJAX pozadavku?
Klasicke PHP session si s tim moc dobre neporadi, coz muze zpusobovat (a mne zpusobilo) osklive problemy.
Napr situace dvou AJAX pozadavku, z nichz jeden se zpracovava dlouho. Behem toho uzivatel klikne na dalsi ajax request, ktery se zpracuje rychle.
Klasicke PHP session nacte session na zacatku a na konci jej ulozi, tedy:
Vysledek: hodnota requestu 2 se ztrati.
Poradi si s timto Nette samo o sobe, nebo je mu potreba nejak pomoci?
Dik za radu
Ako nastavim Nette aby session id nehladalo v Cookie ale v URL?
Pisem aplikaciu pre smartphone a bude pre na ovela jednoduchsie posielat data pomocou GET poziadaviek bez pouzitia COOKIE.