CAPITOLO 5 – PROGETTO PROTOTIPO
5.4 GESTIONE DELLE SESSIONI UTENTE
A seguito del superamento della procedura di autenticazione è necessario fare in modo che il servizio tenga traccia dell’utente, così da fargli visualizzare solo le informazioni di sua competenza ed evitargli inoltre di dover inserire nuovamente le proprie credenziali durante la navigazione tra le pagine dell’Area Protetta. Il protocollo HTTP tuttavia è di tipo stateless, ovvero non mantiene memoria tra una transazione e l’altra anche se queste provengono dallo stesso utente. In altre parole, ogni volta che il browser contatta un server HTTP, è come se lo facesse per la prima volta. Un possibile approccio per far fonte a questo problema è l’utilizzo delle sessioni, che possono essere definite come una sequenza di transazioni HTTP compiute da uno stesso utente e da uno stesso browser in un intervallo di tempo limitato. Una sessione viene identificata tramite il Session ID, che rappresenta una stringa generata in maniera casuale e lunga a sufficienza da rendere improbabile che un’eventuale attaccante possa indovinare i caratteri che vengono generati. Il concetto di sessione è molto simile a quello dei cookie ma presenta una differenza sostanziale: le informazioni sono memorizzate sul server, invece che sul client, al quale viene affidato solamente l’identificativo di sessione. Inoltre, dato che non si inviano dati al client come invece avviene nei cookie, si ha una drastica riduzione del traffico in rete con benefici sia per le prestazioni che per la sicurezza. Ci sono fondamentalmente tre maniere di associare un Session ID ad una sessione:
Salvare il Session ID nel path delle pagine (ad esempio
http://www.miosito.com/11e144e3b510f0c5263e78eff0eb6e4a/home.html);
Associare il Session ID a un parametro della Query String (ad esempio http://www.foo.com/bar?sessid=11e144e3b510f0c5263e78eff0eb6e4a);
Salvare il Session ID in un cookie.
Delle tre, l’ultima modalità è quella più largamente diffusa. Per creare un oggetto di tipo Session è stato utilizzato il metodo getSession() della classe HttpServletRequest. Questa servlet tenta prima di ricavare la sessione se questa è già presente, altrimenti ne crea una nuova. Il Session ID viene utilizzato per associare ulteriori dati alla sessione; questi dati non possono essere salvati in un cookie in maniera analoga al Session ID perché così facendo andrebbero a risiedere nella macchina del client, e quindi non sarebbero considerati al sicuro. Per questo motivo i dati sensibili vengono salvati lato server, al riparo da occhi indiscreti. Il Session ID costituisce dunque una chiave che permette di recuperare i dati di sessione in un apposito Session Store, che può essere implementato:
In un file locale, il cui nome contiene il Session ID;
In un cookie criptato;
In una tabella del database, la cui chiave primaria è il Session ID.
La prima opzione è semplice da implementare ma presenta il difetto che se il servizio web è distribuito su più di un server è necessario mettere il file su un filesystem di rete, che è meno performante ed affidabile di un filesystem locale. La seconda opzione prevede di criptare i dati di sessione con una chiave casuale molto lunga, in modo che anche se i dati risiedono sulla macchina dell'utente, decriptarli sia proibitivamente costoso in termini computazionali. La terza opzione, che è quella che è stata adottata, è anch'essa molto semplice da realizzare, la più robusta, con poco overhead e permette di rendere il servizio scalabile. L’implementazione del Session Store ha dunque previsto la creazione della seguente tabella nel database:
CREATE TABLE SESSION_STORE(
ID NUMBER(38,0) NOT NULL ENABLE,
SESSION_ID VARCHAR2(32 BYTE) NOT NULL ENABLE, USER_ID VARCHAR2(16 BYTE) NOT NULL ENABLE, REMOTE_IP VARCHAR2(39 BYTE) NOT NULL ENABLE,
TIMESTAMP TIMESTAMP (6) DEFAULT systimestamp NOT NULL ENABLE, CONSTRAINT "SESSIONI_PK" PRIMARY KEY ("ID")
);
La minimizzazione delle informazioni salvate ha permesso di aumentare l’efficienza del servizio e limitarne l’occupazione di spazio in memoria. L'algoritmo per verificare se chi tenta di accedere all’Area Protetta ha una sessione valida o meno (e quindi verificare se è passato o meno dalla procedura di autenticazione) è il seguente:
String query ="SELECT * FROM SESSION_STORE WHERE SESSION_ID='"+session.getId()+"' AND USER_ID='"+session.getAttribute("username")+"' AND REMOTE_IP='"+ipAddress+"'"; Statement st= connection.createStatement(); rs=st.executeQuery(query); if (!rs.next()){
//Sessione non creata. Si viene redindirizzati verso la pagina di Login. } else{
//Accesso consentito. }
Tali verifiche vengono fatte con il supporto di un “filtro di autenticazione”. Si tratta di uno strumento simile ad una servlet, introdotto con la versione 2.3 delle API; formalmente i filtri si definiscono come dei preprocessori di una richiesta HTTP. Nello specifico vengono analizzate tutte le richieste dirette alle pagine dell’Area Protetta, verificando la corrispondenza tra i dati presenti in sessione e quelli inseriti nel Session Store ed effettuando un re-indirizzamento dinamico verso la pagina di login dei non aventi diritto all’accesso. All’interno del file web.xml, tramite il tag <filter- mapping>, viene specificato al server HTTP quando tale filtro deve essere eseguito. Lo schema logico di funzionamento del filtro che pre-processa le richieste è il seguente:
Client Servlet Filter Web
Component
1: request
3: chain.doFilter 2: process
4: response
Si vede come il meccanismo implementato fornisca una protezione a certe risorse contenute nel web server. In questo contesto diventa fondamentale anche la protezione da attacchi Session Hijacking, ovvero lo sfruttamento di una normale sessione di lavoro per raggiungere un accesso non autorizzato alle informazioni e ai servizi messi a disposizione. Tale protezione avviene cercando di prevenire l’identificazione del Session ID dell’utente secondo la seguente successione logica di operazioni: l’utente inserisce le credenziali corrette, il sistema autentica l’utente, la sessione attuale viene invalidata, viene creata una nuova sessione (quindi nuovo Session ID non accessibile al malintenzionato), vengono caricati i dati in sessione, l’utente entra nella sua area privata con il nuovo Session ID. Si sottolinea comunque che è stato ritenuto opportuno invalidare le sessioni anche dopo un certo periodo di tempo prestabilito, o in caso di sessione inattiva prolungata. L’invalidazione avviene tramite il metodo invalidate(), oppure in maniera automatica se questa supera il tempo massimo di inattività/validità previsto. Invalidare una sessione significa eliminare l’oggetto di tipo HttpSession e quindi tutti i dati in esso memorizzati. Questa procedura, per una maggiore garanzia di sicurezza, deve essere preferibilmente fatta anche al logout dell’utente. Per cui, se l’utente decide di chiudere la pagina del browser senza aver prima effettuato il logout, viene visualizzata una finestra a video che lo esorta a farlo, portando così a termine le operazioni in maniera corretta.