Sessioni

44. Gestione avanzata delle Sessioni

Con questo capitolo concludiamo il discorso sulle Sessioni, costruendo una libreria esterna che ci consentirà un maggiore controllo sulle operazioni che ne coinvolgono la gestione.

Di default PHP salva i dati di sessione nella directory /tmp/ che è solitamente accessibile anche dall'esterno, questo potrebbe comportare seri problemi di sicurezza a seconda dei dati che intendete tenere in memoria nelle sessioni.

Definirò di seguito dei gestori che saranno automaticamente chiamati da PHP al verificarsi di un determinato evento di sessione.

Per definire quale gestore deve essere chiamato per quel determinato evento abbiamo a disposizione la funzione nativa session_set_save_handler().


Prototipo della funzione session_set_save_handler()

void session_set_save_handler ( string apertura, string chiusura, string lettura, string scrittura, string distruzione, string gc )

I parametri di questa funzione sono delle stringhe il cui nome rappresenta una funzione definita dall'utente da richiamare al verificarsi di quel determinato evento di sessione :

  • apertura - Chiamata ad inizio sessione da session_start()
    Prende due parametri : "path" e "nome", rispettivamente il percorso per i file di sessione e il nome della sessione corrente appena inizializzata.
    Deve restituire true se l'apertura della sessione è avvenuta con successo.
  • chiusura - Chiamata a fine sessione
    Non prende paramentri e deve restituire true se la chiusura della sessione è avvenuta con successo.
  • lettura - Chiamata dopo "apertura" per leggere i dati di sessione e renderli disponibili allo script tramite l'array globale $_SESSION[]
    Prende solo il parametro "id", ossia l'id della sessione corrente che sarà utilizzato per accedere ai dati di sessione.
    Dovrà restituire una stringa che rappresenta la serializzazione dell'array $_SESSION[] oppure una stringa vuota.
  • scrittura - Chiamata quando viene scritto un dato di sessione (es. $_SESSION["dato"] = 3)
    Prende due parametri : "id" e "dati", rispettivamente l'id della sessione e tutti i dati da memorizzare (l'intero contenuto di $_SESSION[]).
    Deve restituire true se la scrittura è avvenuta con successo.
  • distruzione - Chiamata da session_destroy()
    Prende solo il parametro "id" ossia l'id della sessione corrente e deve restituire se la distruzione è avvenuta con successo.
  • gc - Garbage Collector, viene chiamata casualmente per controllare che la sessione non sia scaduta
    Prende solo il parametro "max_lifetime" che rappresenta la durata della sessione in secondi (default 1440 secondi, quindi 24 minuti).
    Deve restituire true per proseguire.

Prima di iniziare a scrivere le funzioni di gestione è necessario configurare alcuni settaggi per far funzionare correttamente lo script.

Innanzitutto dobbiamo fare in modo che la chiamata a session_set_save_handler() vada a buon fine, impostando il parametro "session.save_handler" al valore "user". Trovate il parametro nel file di configurazione php.ini.

Se non potete accedere al file php.ini o volete settare questa impostazione solo per lo script corrente allora ci affidiamo come sempre alla funzione ini_set().

Vediamo ora il file sorgente della piccola libreria di esempio (sessioni.php) che ho costruito, ovviamente incompleta essendo il suo scopo puramente didattico.

sessioni.php

<?php

	// Attiva i cookie di sessione
	ini_set("session.use_cookies", 1); 

	// Accetta ID solo da cookie, rifiuta se vengono propagati da URL
	ini_set("session.use_only_cookies", 1);

	// Informa PHP che dovrà usare dei gestori definiti da noi
	ini_set("session.save_handler", "user");

	// Impostiamo la cartella del server che dovrà contenere i file di sessione
	ini_set("session.save_path", "C:/Appserv/www/test/sessioni");

	define("FILE_PREFISSO", "sess_"); // Inizio nome dei file di sessione
	define("FILE_ESTENSIONE", ".txt"); // Estensione dei file di sessione

	$session_file = NULL;

	function ss_apertura($path, $nome)
	{
		session_cache_expire(60 * 24); // I file di sessione sono validi per 24 ore
		session_set_cookie_params(60 * 60 * 24); // Il cookie di sessione dura 24 ore
		return true;
	}

	function ss_chiusura()
	{
		global $session_file;
		return @fclose($session_file); // Chiude il puntatore al file di sessione aperto
	}

	function ss_lettura($id)
	{
		$id = session_id();
		$filename = session_save_path() . "/" . FILE_PREFISSO . $id . FILE_ESTENSIONE;

		// Tenta di leggere il contenuto del file. Se il file non esiste lo crea
		$session_data = @file_get_contents($filename);

		if (strlen($session_data))
		{ return $session_data; }
		else
		{ return ""; }
	}

	function ss_scrittura($id, $dati)
	{
		global $session_file;

		$id = session_id();
		$filename = session_save_path() . "/" . FILE_PREFISSO . $id . FILE_ESTENSIONE;

		try
		{
			$session_file = @fopen($filename, "w+");
			@fwrite($session_file, $dati); // Scriviamo nel file i nuovi dati di sessione

			return true;
		}
		catch (Exception $e)
		{ return false; }
	}

	function ss_distruzione($id)
	{
		$filename = session_save_path() . "/" . FILE_PREFISSO . $id . FILE_ESTENSIONE;
		return @unlink($filename); // Cancella il file
	}

	function ss_spazzino($max_lifetime) { return true; }

	session_set_save_handler("ss_apertura", "ss_chiusura", "ss_lettura", "ss_scrittura", "ss_distruzione", "ss_spazzino");
	session_start();

?>

Il codice è piuttosto semplice e salva tutti i file di sessione all'interno di file di testo in una cartella "sessioni".

La libreria essendo solo dimostrativa non gestisce alcuna eccezione, ho infatti messo la chiocciola @ prima di quelle funzioni che potrebbero sollevare errori o avvertenze, che farebbero fallire l'invio degli header non avendo bufferizzato l'output con ob_start().

Come potete vedere dalle funzioni ss_lettura() e ss_scrittura, la serializzazione dei dati viene gestita automaticamente da PHP, non vi è quindi alcuna necessità di chiamare altre funzioni, lo scopo di questi due gestori è solo di scrivere e leggere i dati di sessione già pronti.

Di seguito due pagine di esempio : "index.php" e "test.php", la prima scrive i dati di sessione e la seconda li legge.

index.php

<?php

	require_once("sessioni.php");

	$_SESSION["nome"] = "Mario";
	$_SESSION["cognome"] = "Rossi";

?>

<a href="test.php">Test</a>

test.php

<?php

	require_once("sessioni.php");

	echo "Nome = " . $_SESSION["nome"] . "<br />";
	echo "Cognome = " . $_SESSION["cognome"] . "<br />";

?>

Eseguendo index.php viene inizializzata la sessione a partire dal cookie di sessione che come potete vedere dalla schermata non scadrà alla chiusura del browser ma 24 ore dopo. Il cookie come sempre conterrà l'id di sessione che sarà utilizzato per dare un nome univoco al file di sessione (schermata) che conterrà i dati di sessione già serializzati (schermata).


Osservazioni

Ho volutamente lasciato incompleta la funzione ss_spazzino() che impersona il Garbage Collector perchè non l'ho ritenuta utile per questo esempio con i file.

A mio avviso questo sistema di gestione delle sessioni può tornare molto utile e versatile qualora si possedesse un database MySQL, in modo da memorizzare le sessioni nella base di dati, non solo rendendo l'applicazione più sicura, ma consentendovi di condividere i dati delle sessioni anche con applicazioni esterne al server, utilizzando quindi gli stessi dati da più siti contemporaneamente.

Ho creato un esempio semplice con i file invece di usare MySQL, per darvi modo di prendere dimestichezza con queste funzioni.

Vi consiglio pertanto di tornare a dare una spolverata a questo capitolo non appena avrete imparato ad utilizzare un database MySQL, e allora avrete modo di costruirvi una libreria vostra molto più potente e utile di questa.


Il Garbage Collector

Quando definirete una funzione per il Garbage Collector, vi sarà utile sapere per la fase di debug che questa funzione verrà chiamata casualmente in base a dei parametri contenuti nel file di configurazione php.ini :

  • session.gc_maxlifetime - Specifica i secondi dopo i quali i dati non saranno più validi
    Di default è impostato a 1440 secondi. Vi consiglio di tenere in memoria da qualche parte (es. variabile globale) il timestamp di quando è nata la sessione, in modo da verificare rapidamente con un semplice confronto se la sessione è scaduta, sommando al timestamp di nascita il valore maxlifetime passato come parametro al Garbage Collector. Se sarà minore del timestamp corrente allora la sessione è scaduta.
  • session.gc_divisor - Usato per il calcolo della probabilità che venga chiamato il Garbage Collector
    Legato a "session.gc_probability", vedi sotto.
  • session.gc_probability - Usato per il calcolo della probabilità che venga chiamato il Garbage Collector
    I parametri "gc_divisor" e "gc_probability" sono di default settati rispettivamente a 100 e 1.
    Questo significa che c'è una probabilità dell'1% che venga chiamato il garbage collector, o meglio ogni 100 esecuzioni dello script.

In fase di debug vi consiglio quindi di impostare i parametri come specificato di seguito :

  • session.gc_maxlifetime - 60 secondi invece di 1440, così non dovrete aspettare 24 minuti ogni volta che controllate se "pulisce"
  • session.gc_divisor - Lasciate il valore inalterato a 100
  • session.gc_probability - Impostate il valore a 100 come gc_divisor, in modo che il Garbage Collector venga chiamato ad ogni esecuzione dello script e vi dia il modo di verificarne immediatamente il funzionamento

Anche queste impostazioni possono essere modificate tramite ini_set().

Nota : ricordatevi che potete usare $_SESSION[] come un normale array, memorizzandovi quindi anche dati complessi :

<?php

	$_SESSION["utente"] = new Utente("Mario", "Rossi");

?>

43. Le Sessioni

Il protocollo HTTP è stateless, ossia "privo di stato", il che significa che navigando un sito il navigatore effettua delle richieste al server che vengono di volta in volta terminate, rendendo tutte le connessioni "uguali" agli occhi del server senza poter stabilire quali provengono effettivamente dal medesimo utente.

Le sessioni sono molto simili ai cookie e ci consentono anche essi di tenere in memoria dei dati su un determinato utente.

Per questi motivi i cookie e le sessioni sono così largamente utilizzati dai Web Developer, rendendo ormai piuttosto raro trovare un navigatore che non accetta i cookie.

Tenere traccia dello stato di un utente è molto importante per costruire delle applicazioni web efficaci, come ad esempio un sito e-commerce dove è necessario "ricordarsi" il contenuto del carrello dell'utente mentre questo naviga il catalogo dei prodotti.

PHP ci fornisce delle funzioni molto utili per assegnare un ID di sessione univoco ad ogni utente, e ci consente di tenerne traccia sostanzialmente in due modi : passando l'ID della sessione negli URL (come accade nell'invio dei dati con GET) oppure memorizzandolo in un cookie dedicato.

Per motivi di sicurezza vi sconsiglio decisamente il primo metodo, usando le sessioni sempre tramite cookie.

Nelle impostazioni di default, PHP è settato per memorizzare le sessioni in un cookie, non dovreste quindi dover apportare alcuna modifica al vostro server.

A questo punto potremmo anche descrivere una sessione come un cookie che scade alla fine di una "sessione browser", considerando la fine di una sessione browser come la chiusura di quest ultimo da parte dell'utente.

Un'altra differenza molto importante fra cookie e sessioni è dove effettivamente vengono memorizzati i dati.

I cookie, come spiegato nei capitoli precedenti, memorizzano i dati nel browser dell'utente (lato client) mentre le sessioni le memorizzano direttamente sul server, tenendo nel lato client solamente l'ID della sessione, ID che sarà utilizzato poi per risalire ai dati memorizzati sul server.

Questo rende le sessioni molto più indicate quando è necessario gestire dei dati sensibili, dal momento che i cookie e il loro contenuto sono sempre accessibili dall'utente e quindi anche dai linguaggi di scripting client side, come ad esempio Javascript, consentendo ai malintenzionati di apportare modifiche al cookie e ai suoi dati mettendo a serio rischio la sicurezza della vostra applicazione.

Usando le sessioni inoltre non andrete incontro ai problemi di spazio che avreste con i cookie, che come ho scritto due capitoli addietro, qualora un browser rispetti gli standard, non è possibile creare un cookie più grande di 4KB per un massimo di 20 cookie per dominio e un totale di 200 cookie in un browser.


Esempio Sessioni PHP

Vedremo ora un esempio molto semplice dove illustrerò uno scambio di dati fra due pagine mediante l'uso delle sessioni.

index.php

<?php

	session_start();

	$_SESSION["utente"] = "Mario Rossi";

?>

<a href="test.php">Test</a>

test.php

<?php

	session_start();

	if (isset($_SESSION["utente"]))
		echo "Utente = " . $_SESSION["utente"];
	else
	{
		echo "Utente non trovato.<br>\n";
		echo "Andare prima <a href=\"index.php\">qui</a>.";
	}

?>

Come possiamo osservare nell'esempio soprastante, per utilizzare le sessioni è sufficiente chiamare la funzione session_start() in tutte le pagine dove si intende accedere ai dati sessione, e l'array globale $_SESSION[] viene reso automaticamente disponibile.

La prima volta che viene chiamata session_start() viene creato un cookie di nome PHPSESSID, il cui contenuto sarà l'ID di sessione univoco assegnatoci dal server, e che sarà poi utilizzato da quest ultimo per accedere ai dati di sessione.

Se state utilizzando Firefox, una volta eseguita una delle due pagine di esempio, dalla console "Mostra i cookie" avrete modo di leggere il cookie di sessione.

Se eseguirete per prima la pagina index.php, verrà inizializzata la sessione memorizzandovi il dato "utente" e verrà infine stampato il link per accedere alla seconda pagina ossia test.php.

Se invece eseguirete per prima test.php, la pagina stamperà una notifica (Utente non trovato) e a seguire un link per index.php dove sarà memorizzato il dato "utente".

Riavviando il browser la sessione verrà automaticamente terminata, oppure è possibile eliminarla volontariamente in questo modo :

<?php

	session_start();

	unset($_SESSION["utente"]); // Da usare per eliminare un solo dato di sessione, in questo caso "utente"

	// Da usare per distruggere completamente la sessione
	$_SESSION = array(); // oppure chiamate session_unset();
	session_destroy();

?>

In questo capitolo osserveremo più da vicino le sessioni, studiando alcuni settaggi e funzioni che ritengo di maggiore rilievo e che ci consentiranno un maggiore controllo sulla nostra applicazione.

Iniziamo dal file di configurazione php.ini, attraverso cui potremo personalizzare il comportamento del server.

  • session.save_path - La cartella del server dove andranno memorizzati i file di sessione.
  • session.use_cookies - Di default è impostato a 1, il che significa che il server tenterà sempre di memorizzare l'ID di sessione in un cookie nel browser dell'utente.
  • session.use_only_cookies - Di default è impostato a 0, ma vi consiglio di assegnarlo a 1 in modo da impedire attacchi dove vengono passati gli ID di sessione tramite URL. In questo caso però abbiate cura di eseguire sempre dei controlli sull'abilitazione dei cookie, e in caso di esito negativo notificate l'utente sulla necessità di avere i cookie abilitati per poter usufruire appieno del servizio da voi offerto.
  • session.name - Il nome di default della sessione, solitamente PHPSESSID.
  • session.cookie_lifetime - E' impostato a 0 di default eliminando il cookie di sessione alla chiusura del browser. Se avete necessità di rendere disponibile la sessione anche dopo la chiusura del browser allora impostate il parametro con i secondi di durata desiderati.
  • session.cache_expire - La durata in minuti della cache sul server, solitamente impostata a 3 ore.

Se non avete la possibilità di modificare il php.ini del vostro server, oppure avete più applicazioni sullo stesso server con esigenze diverse, allora potete impostare un comportamento diverso per ogni applicazione richiamando direttamente nello script le funzioni native che seguono :

  • string session_save_path ([string path]) - Se non viene specificato alcun parametro la funzione restituisce la path dove vengono salvati i file di sessione. Se invece specificate un parametro viene cambiato il percorso dove vengono salvati i file con quello nuovo.
  • string session_name ([string name]) - Restituisce il nome della sessione corrente. Se specificato un parametro invece imposta il nome di sessione con il nuovo.
  • void session_set_cookie_params (int lifetime [, string path [, string domain]]) - Il primo parametro è obbligatorio e definisce la durata in secondi del cookie di sessione. Gli altri due parametri facoltativi cambiano momentaneamente i rispettivi settaggi nel php.ini.
  • int session_cache_expire ([int cache_expire]) - Restituisce la durata in minuti della cache di sessione, oppure se impostate un parametro il valore corrente della scadenza cache verrà sostituito col nuovo.
  • string session_id ([string id]) - Ritorna l'ID di sessione corrente oppure se impostate un parametro sostituisce l'id corrente col nuovo.
  • void session_write_close (void) - Termina la sessione corrente e ne archivia i dati.

Per una descrizione dettagliata delle funzioni sopracitate e per l'elenco completo vi rimando alla Documentazione ufficiale di PHP.

Attraverso queste funzioni non è però possibile modificare due dei parametri più importanti :
"session.use_cookies" e "session.use_only_cookies".

Per modificare queste due impostanzioni ricorreremo alla funzione ini_set() come mostrato di seguito :

<?php

	ini_set('session.use_cookies', 1);
	ini_set('session.use_only_cookies', 1);

?>

Prima di chiudere il capitolo vediamo come può tornarci utile la funzione session_write_close(), aumentando le prestazioni del server con applicazioni che scrivono grandi quantità di dati per sessione.

E' necessario sapere che i dati di sessione vengono bloccati per evitare che più script scrivano contemporaneamente in essi, fino a che viene terminato uno script e viene concesso il permesso al successivo.

Di default PHP archivia i dati di sessione al termine dello script, ma è utile anticiparne l'archiviazione qualora utilizzaste ad esempio i frameset, che verranno caricati uno alla volta proprio a causa di questo blocco.

Non appena avete assegnato tutti i valori ai dati di sessione quindi, sarà bene terminare quest'ultima anticipando l'archiviazione dei dati mediante una chiamata a session_write_close(), per consentire l'accesso al prossimo script.

Nel prossimo capitolo vedremo come definire delle funzioni personalizzate per gestire in modo avanzato le sessioni.

Condividi contenuti