Edit
Lang

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. 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.