Spesso si presenta l'esigenza di dover restringere l'accessibilità di una o più pagine di un applicazione web a una ristretta cerchia di utenti. Molti siti sono composti da una parte visibile a tutti i visitatori e da una sezione privata che può essere acceduta soltanto dagli amministratori dell'applicazione attraverso le opportune credenziali di autenticazione.
Quando parliamo di autenticazione, possiamo far riferimento a un accesso privilegiato per alcuni utenti a determinate sezioni di un'applicazione web.
Rimanendo nel campo del mondo Java, la prima soluzione che salta alla mente è quella di gestire le funzionalità richieste dal processo di autenticazione direttamente dalle pagine JSP o dalle servlet. Questa soluzione non è banale e oltretutto non vi è, solitamente, motivo per non dover sfruttare le peculiarità degli application server o dei servlet container (come Tomcat) che mettono a disposizione questi servizi in maniera efficiente. La gestione dell'autenticazione a livello di servlet container viene definita container-managed security.
Come funziona l'autenticazione gestita dal container
Vediamo quello che accade nella pratica quando un client invia una richiesta al server, relativamente a una risorsa con accesso riservato. Riprendiamo, a tal proposito, l'applicazione web di esempio che abbiamo utilizzato nell'articolo su JavaMail e supponiamo di voler ora limitare l'utilizzo della Servlet MailServlet
soltanto a un certo numero di utenti. Quello che accade è che, dopo aver compilato la form HTML iniziale e cliccato sul pulsante Invia, apparirà la seguente finestra di dialogo:
Se l'utente connesso inserirà le corrette credenziali di accesso, allora, sarà possibile accedere alla risorsa richiesta (ovvero alla servlet MailServlet
) e, poi, inviare il messaggio di posta elettronica all'indirizzo desiderato. Al contrario, se verranno digitate per tre volte consecutive delle credenziali non autorizzate (o si clicca sul pulsante Annulla), il risultato sarà il seguente:
Ovvero, l'utente non sarà abilitato ad accedere alla risorsa richiesta.
In generale, per restringere l'accesso a un risorsa web, è necessario impostare una security constraint
nel deployment descriptor dell'applicazione. Tale constraint conterrà il nome della risorsa per la quale è richiesta un'autenticazione. Così, quando un client richiederà di accedere a una risorsa protetta, Tomcat lo informerà sui vincoli richiesti dalla risorsa stessa e il browser visualizzerà la finestra di dialogo vista in precedenza.
Le tipologie di Autenticazione
È possibile individuare tre tipologie comuni di autenticazione:
- Basic Authentication: Il browser visualizza una finestra di dialogo (come quella vista precedentemente) che richiede l'inserimento di una username e di una password. Tali credenziali vengono quindi inviate, in formato testo (plain-text), al server in maniera tale che quest'ultimo possa riconoscerle e autenticarle. Questa tipologia di autenticazione è certamente quella più facile da implementare, non richiede nessuna aggiunta di codice da parte del programmatore ed è ampiamente supportata dalla maggior parte dei browser. Tuttavia, proprio a causa del fatto che le informazioni vengono inviate in chiaro al server, la Basic Autenthication non garantisce dal rischio di intercettazioni da parte di malintenzionati.
- Digest Authentication: Si basa sullo stesso processo della Basic Authentication, con la differenza che le credenziali inviate al server vengono convertite, prima dell'invio, in un formato che rappresenta una sorta di impronta digitale delle credenziali stesse. Il concetto del digest è quello che abbiamo esaminato nell'articolo in cui si è parlato delle nozioni di base sulla sicurezza (e in particolare dei Message Digest). Pur essendo più sicuro del precedente metodo di autenticazione, la Digest Authentication ha lo svantaggio di non essere supportata dalla maggior parte dei browser. Oltretutto, la garanzia offerta in tema di sicurezza non è comunque elevata e non è certo paragonabile a quella offerta da una connessione di tipo SSL.
- Form-based Authentication: Utilizza una web form personalizzabile a scelta del programmatore, al posto della finestra di dialogo visualizzata nelle due precedenti tipologie di autenticazione. Sebbene la form-based authentication non preveda in automatico la crittografia delle credenziali da inviare al server, è possibile utilizzare (se la sicurezza rappresenta un fattore critico dell'applicazione) una connessione SSL e fornire così un alto livello di protezione relativamente allo scambio di informazioni tra client e server. Questa tipologia di autenticazione è supportata dalla maggior parte dei browser.
Le impostazioni necessarie sul Deployment Descriptor
Per restringere l'accesso a una risorsa web è necessario aggiungere, innanzitutto, un elemento di tipo security-constraint
sul file web.xml. Per esempio, tornando all'applicazione che fa uso delle API di JavaMail, se volessimo restringere l'accesso a tutte le risorse web contenute all'interno della directory MailServlet, potremmo scrivere:
Listato 1. Restringe l'accesso a tutte le risorse all'interno di MailServlet
<security-constraint>
<web-resource-collection>
<web-resource-name>Area Riservata</web-resource-name>
<url-pattern>/MailServlet/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
È importante specificare, all'interno dell'elemento url-pattern
, una URL che faccia riferimento a uno o più file. Per specificare un singolo file sarà necessario indicarne la URL relativamente al document root. Per applicare, invece, la restrizione su tutti i file appartenenti a una certa directory, sarà sufficiente utilizzare il carattere asterisco (proprio come nell'esempio di cui sopra).
L'elemento http-method
consente di applicare una restrizione di accesso soltanto a un particolare metodo HTTP. Nel nostro esempio, la restrizione verrà applicata sia al metodo GET che al metodo POST. Se si desidera, tuttavia, applicare la restrizione a ogni tipo di richiesta verso una certa risorsa è sufficiente omettere gli elementi di tipo http-method.
Per stabilire quali utenti possano accedere a una risorsa riservata, è necessario utilizzare un elemento di tipo auth-constraint. All'interno di tale elemento vanno definiti i ruoli (roles) degli utenti che possono accedere alla risorsa. Nell'esempio precedente, gli utenti associati al ruolo admin potranno accedere ai file presenti nella directory MailServlet.
È importante ricordare che, all'interno del file web.xml (vedasi, per maggiori dettagli, l'articolo sul deployment descriptor) l'elemento security-contraint
deve essere definito dopo gli elementi servlet
, servlet-mapping
e error-page
.
Il realm
Quando si utilizza Tomcat per gestire la sicurezza, è necessario specificare quale tipo di realm si desideri implementare. Per coloro che non avessero ben chiaro cosa sia il realm, basterà sapere che esso rappresenta il meccanismo che serve a identificare gli utenti validi. Su Tomcat è possibile scegliere fra tre tipologie di security realms.
Per scoprire come e dove sono definite tali tipologie sarà necessario andare a visualizzare il file server.xml, localizzato all'interno della directory conf di Tomcat. In particolare, andremo a cercare la sezione del file in cui sono contenuti gli elementi di tipo realm, ovvero (in Tomcat 5.5.17) la seguente:
Listato 2. Sezione di server.xml dove sono contenuti gli elementi di tipo realm (Guarda il codice completo)
..//
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase" />
<!--
Comment out the old realm but leave here for now in case we
need to go back quickly
-->
<!--
<Realm className="org.apache.catalina.realm.MemoryRealm" />
-->
..//
<Realm className="org.apache.catalina.realm.JDBCRealm"
driverName="org.gjt.mm.mysql.Driver"
connectionURL="jdbc:mysql://localhost/authority"
connectionName="test" connectionPassword="test"
userTable="users" userNameCol="user_name" userCredCol="user_pass"
userRoleTable="user_roles" roleNameCol="role_name" />
-->
..//
Osservando i commenti presenti sul codice XML, si intuisce che il realm attivo di default è quello che utilizza la risorsa denominata UserDatabase, all'interno dello stesso file server.xml.
Andando a vedere come è definita tale risorsa:
Listato 3. Risorsa UserDatabase all'interno di server.xml
<!--
Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" />
è facile concludere che è essa è basata sul file tomcat-users.xml.
Il memory realm, invece, conserva le username e le password direttamente in memoria. Solitamente, però, questa tipologia di realm viene utilizzata soltanto a scopi di test.
Infine, il JDBC realm, permette di salvare le username, le password e i roles degli utenti validi all'interno di un database relazionale. Nei commenti presenti sullo spezzone di codice XML relativo alla definizione del realm, si possono scorgere tre impostazioni disponibili di JDBC realm dalle quali è possibile sceglierne una da utilizzare e personalizzare.
La Basic Authentication in pratica
Lasciamo inalterato il file server.xml relativamente all'impostazione del realm. Andiamo, quindi, a osservare il file tomcat-users.xml, scegliendo un utente e un ruolo già definito che potrà accedere alla servlet MailServlet:
Listato 4. File tomcat-users.xml con utente e ruolo per accedere alla servlet MailServlet
<?xml version="1.0" encoding="utf-8" ?>
<tomcat-users>
<role rolename="tomcat" />
<role rolename="role1" />
<role rolename="manager" />
<role rolename="admin" />
<user username="tomcat" password="tomcat" roles="tomcat" />
<user username="both" password="tomcat" roles="tomcat,role1" />
<user username="role1" password="tomcat" roles="role1" />
<user username="admin" password="admin" roles="admin,manager" />
</tomcat-users>
Modifichiamo, quindi, il web.xml dell'applicazione che utilizza JavaMail, nel seguente modo:
Listato 5. Web.xml dell'applicazione che utilizza JavaMail (Guarda il codice completo)
..//
<servlet-mapping>
<servlet-name>MailServlet</servlet-name>
<url-pattern>/MailServlet</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Area Riservata</web-resource-name>
<url-pattern>/MailServlet/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Richiesta Autenticazione</realm-name>
</login-config>
..//
In rosso abbiamo evidenziato le modifiche apportate rispetto alla versione dell'articolo su JavaMail.
Come si può notare, oltre all'elemento security-constraints
, di cui abbiamo parlato finora, è necessario aggiungere anche un elemento di tipo login-config
, all'interno del quale auth-method
indica la tipologia di autenticazione da applicare e realm-name
contiene la stringa da visualizzare sulla finestra di dialogo mostrata dal browser.
Effettuiamo, quindi, il deployment dell'applicazione e mandiamola in esecuzione. Compiliamo la pagina html con i dati necessari all'invio, come riportato nella figura seguente:
E clicchiamo sul pulsante Invia:
Sulla finestra di dialogo che richiede la username e la password inseriamo le credenziali definite nel file tomcat-users.xml per l'utente admin:
Cliccando sul pulsante "Ok" otterremo l'invio del nostro messaggio di posta elettronica al destinatario prescelto.
La Form-based Authentication in pratica
Vediamo, adesso, come riproporre lo stesso esempio utilizzando la form-based authentication. In questo caso, sarà necessario costruire una pagina di Login personalizzata, ma che contenga sempre almeno la username, la password e un pulsante di Submit verso il server
Una semplice pagina di Login, potrebbe essere la seguente:
Listato 6. Pagina html con un form per il Login
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Login necessario per l'invio di email</title>
</head>
<body>
<h1>Login</h1>
<p>Inserire la propria username e password</p>
<table cellspacing="5" border="0">
<form action="j_security_check" METHOD="get">
<tr>
<td align="right">Username</td>
<td><input type="text" name="j_username"></td>
</tr>
<tr>
<td align="right">Password</td>
<td><input type="password" name="j_password"></td>
</tr>
<tr><td><input type="submit" value="Invia"></td></tr>
</form>
</table>
</body>
</html>
Si notino i tre attributi evidenziati in rosso nel codice HTML. I valori associati a essi:
- "j_security_check"
- "j_username"
- "j_password"
devono essere utilizzati obbligatoriamente quando si costruisce una form di autenticazione.
L'ultima cosa da fare è apportare la seguente modifica all'elemento <login-config>
del deployment descriptor:
Listato 7. Modifiche da apportare a <login-config> in web.xml
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>login.html</form-login-page>
<form-error-page>login.html</form-error-page>
</form-login-config>
</login-config>
Si noti che in questo caso si è scelto di utilizzare la stessa pagina html sia come pagina di autenticazione che come pagina di errore (ovvero, nel caso di autenticazione fallita si ritorna alla stessa pagina).
Eseguiamo nuovamente l'applicazione. Dopo aver inserito i dati relativi al messaggio di posta elettronica da inviare nella pagina index.html, apparirà, questa volta, la nostra form di autenticazione:
Inseriamo le credenziali di accesso (admin, admin) e clicchiamo sul pulsante Invia. Se i dati inseriti sono corretti, la servlet viene invocata correttamente e il messaggio spedito al destinatario.