Un meccanismo di gestione accessi per le applicazioni JSP
Abstract.
La problematica esposta nasce da un'esperienza reale: lo sviluppatore
ha "ereditato" un'applicazione web senza restrizioni di accesso
e il responsabile delle informazioni ha deciso di limitare gli accessi.
Varie e variegate sono le motivazioni che portano a imporre restrizioni
di accesso ad un'applicazione web oppure a un sito (o anche solo una parte
di questo) .le ragioni di questa scelta esulano spesso dalla riservatezza
o sicurezza per sfociare addirittura nel marketing.
Nel corso di questa mini serie affronteremo le principali problematiche
sulle autenticazione e sicurezza; realizzeremo alcuni meccanismi di accesso
sicuro (incluso, nell’ultima parte, l’accesso tramite certificato
digitale); implementeremo i necessari tool a corredo per la gestione e
il rilascio dei permessi, per la registrazione on line e per tenere traccia
delle operazioni svolte dagli utenti sul nostro sito.
Lo scenario iniziale.
Siamo davanti all’esigenza di rendere "controllata" un'applicazione
che non prevede nessuna forma di verifica degli accessi.
Lo sviluppatore è chiamato ad intervenire su un’applicazione
preesistente. L'introduzione di una nuova funzionalità potrebbe
richiedere parecchie piccole modifiche o, meglio ancora, la reingegnerizzazione
di tutto il sistema. Inoltre il committente è soddisfatto delle
funzionalità dell'applicazione e non vuole modificarne il codice
esistente: sarebbe infatti esposto ai rischi e alle problematiche di un
periodo di test lungo con costi alti, senza ottenere particolari benefici.
Siamo davanti ad un problema non banale: inserire un meccanismo di autenticazione
e non intervenire a livello di codice dell'applicazione.
All'esigenza di impattare minimamente sul codice si aggiunge anche l'interresse
del programmatore ad individuare una soluzione riusabile.
Avendo posto come vincolo la riusabilità della soluzione individuata,
si può facilmente immaginare che debba essere polivalente, scalabile
,separata logicamente dall'applicazione e flessibile.
A questi vincoli si aggiungono quelli dettati dal buon senso: “fare
tutto ciò che vuole il cliente” senza spesa di tempo e in
modo automatico. Inoltre pa4je essere di semplice istallazione e non necessitare
di particolare manutenzione,infine a corredo sarà necessario prevedere
uno strumento grafico per permettere il rilascio di autorizzazioni a nuovi
utenti,inoltre gestirà il repository degli utenti in modalità
manuale o automatica (come avviene per il rilascio di permessi su siti
con registrazione).
Le principali soluzioni "accademiche"
previste.
Http Basic Authentication e Client-Certificate Authentication sono meccanismi
di autenticazione previsti all’interno del protocollo Http. Oltre
a questi, esistono altri meccanismi di autenticazione: i più importanti
per diffusione sono la Form Based Authentication e l'utilizzo di un portale
di autenticazione. Di seguito ne vedremo velocemente il funzionamento.
L'Http Basic Authentication è di uso abbastanza comune, richiede
che l'utente inserisca la coppia nome utente e password al momento dell'accesso.
Una schematizzazione del dialogo tra browser e server è indicato
in figura 1 .
Semplificando, il meccanismo adottato è il seguente: il browser
richiede una risorsa protetta al server web; il server verifica la presenza
delle credenziali necessari; se queste sono state inviate e sono corrette,
restituisce la risorsa richiesta, altrimenti invia al browser un codice
numerico con il significato: “la risorsa è protetta, per
accedere alla risorsa inviare le proprie credenziali. Il meccanismo di
protezione adottato è l'Http Basic Authentication”.
Alla richiesta delle credenziali, il browser mostra all'utente una maschera
di inserimento nome utente e password .
L'HTTP basic authentication non offre particolari garanzie in termini
di sicurezza: invia username e password su internet come testo Unix-to-Unix
encoded (nato con l’avvento di interne il protocollo Unix-to-Unix
encoded permette l’invio di oggetti binari in formato testo senza
però aggiungere meccanismi di sicurezza come la cifratura). Questo
meccanismo di autenticazione espone le credenziali ad essere intercettate
da qualcuno in ascolto sulla rete, almeno finché tutto il canale
di trasmissione non viene cifrato e posto sotto sicurezza (utilizzando
Http over SSL o più semplicemente https). Per chi fosse in ascolto
sul canale di trasmissione , sarebbe facile decodificare le informazioni
di accesso.
I siti commerciali generalmente non utilizzano L'Http Basic Authentication,
non tanto per problematiche di sicurezza ma perché non permette
di personalizzare la maschera di inserimento dati e l' eventuale pagina
di errore.
In questi casi si predilige la Form-Based Authentication poiché
realizza il colloquio browser server tramite l'uso di form html.
Lo schema dei principali passi della Form-Based Authentication sono rappresentati
in figura 2.
Quando il client richiede una risorsa protetta, il server re-indirizza
la richiesta ad una pagina di login che contiene una form per l'invio
delle credenziali.
Il client spedisce la coppia nome utente e password, quindi il server
valida le credenziali inviate e, in caso di successo, permette l’accesso
altrimenti rinvia a una pagina di errore.
Anche la Form-Based authentication non è particolarmente sicura,
infatti le informazioni per l'acceso sono inviate come testo.
La Form-Based Authentication possiede il vantaggio di essere facilmente
personalizzabile nella sua veste grafica, oltre che nella logica di implementazione.
Ciò consente di inserire all'interno della pagina principale di
un sito la form di autenticazione.
I meccanismi di autenticazione sopra descritti sono “deboli”
e non possono essere utilizzati in caso di trattamento di dati sensibili
o per accesso a sistemi particolarmente delicati.Infatti non siamo sicuri
che la coppia login e password non sia stata “rubata” ad un
nostro utente a sua insaputa.
La risposta all'esigenza di "essere certi che il client sia davvero
chi dice di essere" la troviamo nel protocollo SSL (Socket Secure
Layer) combinato all’uso dei certificati digitali.
Il certificato digitale può essere contenuto all’interno
di chip, come avviene per la carta d’identità elettronica.
Essendo unico, non può essere copiato senza che l’utente
se ne accorga.
Oltre alla crittografia delle comunicazioni il protocollo https prevede
che il server "dichiari sempre la propria identità" inviando
al client un certificato elettronico. Il certificato contiene informazioni
sulla Certification Authority (CA) emittente, sul server, sul periodo
di validità, ecc..
Il client può scegliere se accettare come affidabile la CA che
ha emesso il certificato oppure di non fidarsi e non proseguire il colloquio.
Le CA sono censite e i loro requisiti sono regolamentati per legge, è
possibile approfondire l’argomento sul sito www.digitpa.it.
Il protocollo https prevede due modalità:senza richiesta del certificato
client o con richiesta del certificato client. Analizzeremo quest' ultima
utile per individuare l'utente.
Il protocollo utilizzato è HTTP over SSL (https), nel quale il
server e il client si autenticano l'un l'altro tramite l'uso di certificati
a chiave pubblica. Secure Socket Layer (SSL) garantisce la cifratura dei
dati, l'autenticazione del server,l'integrità dei messaggi e l'autenticazione
del client su una connessione TCP/IP.
É possibile immaginare un certificato a chiave pubblica come l'equivalente
digitale di un documento d’identità.
I meccanismo è piuttosto complesso, in [FIGURA 3] si è tentato
di dare una rappresentazione semplificata.
La Client-Certificate Authentication è un meccanismo di autenticazione
estremamente sicuro e sia il privato che pubblica amministrazione ne fanno
uso. Il graduale rilascio delle Carte d'Identità Elettroniche avrà
come conseguenza una aumento dei servizi con accesso protetto da certificato.
L'istallazione e la configurazione dei componenti necessari non è
però semplice.
Il costo del rilascio di un certificato ad opera di una CA riconosciuta
può essere elevato, tuttavia è possibile "fingersi"
CA e rilasciare certificati che però non saranno considerati attendibili,
se non dai server che amministriamo direttamente.
Tecnologie coinvolte
La tecnologia J2EE prevede un set di componenti specializzati e fortemente
riusabili.
I componenti Web fondamenti sono:
• Il Web Container o servlet container.
• Gli oggetti statici
• Le servlet e le java servlet page (JSP).
• I Java Filter.
I Java Bean e gli Enterprise Java bean sono oggetti di supporto a quelli
web.
I Java Bean, oggetti che possono memorizzare informazioni o includere
piccole funzionalità della logica di business.
Gli Enterprise Java Bean sono oggetti ben più complessi che includono
tutta la logica di business e possono essere ospitati anche presso altri
server (Application Server). La trattazione degli Ejb esula dalle nostre
esigenze.
Il Web container è parte di un web server che fornisce servizi
di rete sui quali richieste e risposte sono inviate.Il Web container è
in grado di esaudire una richiesta di un cliente, tramite invocazioni
a metodi delle servlet e delle pagine Jsp e ai Java Filter, seguendo le
indicazioni presenti nel file descrittore dell'applicazione (web.xml).
Il Web Container ha come compito principale la connessione ed il colloquio
con il client, rende disponibili agli altri componenti oggetti che consentano
l’accesso ai dati della request http e la scrittura sulla response
in modo semplice .Inoltre con il passare del tempo si è richiesto
al web container di esporre altri servizi quali la gestione della configurazione,
alcuni meccanismi di protezione all'accesso alle risorse,l' accesso controllato
ad un data base, gestione del log applicativo, risoluzione dei nomi di
risorse e altri ancora.
Gli oggetti statici sono generalmente file html, immagini e documenti
che l'utente può richiedere al server.
Le servlet e le pagine Jsp a livello architetturale sono simili: entrambi
possono recuperare informazioni dalla request, quindi secondo la propria
logica utilizzare altri oggetti di logica di business e scrivere dati
sulla response, oppure demandare il controllo ad un'altra servlet o una
Jsp.
I due componenti assumono invece un ruolo diverso in ottica di separazione
delle logiche: le servlet sono blocchi di codice java, nei quali è
facile inserire una logica di calcolo o di controllo; mentre le Jsp, che
possono contenere codice html e script in Java, sono utili per includere
la formattazione e la presentazione del codice html da inviare al client.
In figura 4 sono disegnati i componenti descritti e le loro interazioni.
inizieremo a commentare lo schema ignorando la presenza del filtro.
Lo schema si ispira all'architettura Jsp Model 2(che segue il pattern
Model View Control) adottata spesso nella programmazione Jsp. Il pattern
prevede una forte separazione tra componenti in funzione dei compiti da
svolgere:
• gli oggetti che accedono ai dati e li rendono disponibili assumono
il nome di “Model”, contengono la logica di accesso alle risorse;
• gli oggetti che permettono una rappresentazione grafica dei dati
sono chiamati View, contengono la logica di presentazione;
• gli oggetti, che sovrintendono alla comunicazione tra utente,
View e Model” e ne gestiscono le interazioni secondo la logica dell’applicazione
assumono, il nome di Controller.
L’architettura Jsp Model 2 rispecchia le regole del pattern tramite
i seguenti passi:
• Il client invia le richieste ad una servlet con compito di Controller
(indicata con C);
• La servlet individua all’interno della request i parametri
che indicano l’azione richiesta dall’utente (action);
• In funzione dell’action, dello stato della sessione (che,
ad esempio, indica le operazioni già eseguite) o di altre informazioni,
la logica di controllo applicativo incapsulata all’interno del Controller
istanza i java bean necessari;
• I java bean assumono il ruolo di Model interfacciandosi a risorse
esterne ed eseguono “sotto il controllo”della servlet le operazioni
necessarie per portare a buon esito l’azione richiesta;
• La servlet verifica il buon esito delle operazione effettuare
ed individua la pagina Jsp che pa4je essere utilizzata per la visualizzazione
dell’esito di action, la pagina Jsp assume il ruolo di “vista
per l’utente” (View);
• La pagina Jsp svolge il proprio ruolo di presentazione e, utilizzando
i java bean come fonte di informazioni, produce il codice html per la
risposta che viene indirizzata al Client.
In questa sequenza non è stata descritta la logica di autenticazione
e autorizzazione dell’utente, possiamo però immaginare di
utilizzare un oggetto che sia in grado di intercettare la comunicazione
tra client e Controller e che all’occorrenza inibisca la comunicazione,
rendendo le risorse inaccessibili a “personale non autorizzato”.
Il componete fornito dalla tecnologia J2EE che pare soddisfare le nostre
esigenze è un Http Servlet Filter.
Secondo le specifiche, i filtri possono modificare il contenuto delle
richieste http e le informazioni dell'header, non dovrebbero produrre
codice html da inviare al browser, mentre possono intercettare le richieste
ad una risorsa prima che questa sia invocata dal Web Container e, se necessario,
inoltrare la richiesta ad un'altra risorsa senza interessare il blocco
MVC.
Il punto di forza dei filtri è l'essere oggetti semplici, trasparenti,
attivabili senza modificare il codice delle applicazioni già esistenti.
L’implementazione di un filtro pare essere la soluzione alla nostra
esigenza: il blocco MVC e le risorse statiche rappresentano la nostra
applicazione di partenza, il filtro si antepone a questa, senza modificarne
la logica e effettuerà il controllo di autenticazione dell'utente.
La logica del filtro da implementare in breve è la seguente:
• alla richiesta di accesso ad una generica risorsa il filtro verifica
se questa è considerata protetta e a che livello.
• Se la risorsa è protetta, verifica la presenza delle credenziali
di accesso,
• Se le credenziali sono presenti e corrette, permette alla richiesta
di giungere alla risorsa invocata, altrimenti richiede al client di ripetere
la richiesta includendo le credenziali valide per l’accesso.
Nell'ottica di scrivere un componente riusabile anche in applicazioni
MVC pure nelle quali sia prevista la profilazione possiamo imporre ulteriori
vincoli: il filtro renderà disponibile al Controller un semplice
oggetto model che rappresenta l'utente,le impostazioni personalizzate
e i suoi permessi, questo oggetto sarà implementato da un java
bean che implementerà l’interfaccia User del nostro progetto.
Il Controller potrà utilizzare le informazioni relative all’oggetto
per la propria logica di business (ad esempio settando quale lingua dovrà
essere utilizzato per i messaggi di risposta).
Le JSP utilizzeranno il Bean per personalizzare l’aspetto grafico
delle pagine, inserendo un messaggio di benvenuto o per predisporre i
colori dei caratteri e dello sfondo, mentre i Bean di accesso ai dati
possono registrare all’interno del database il nome dell’utente
e quali dati ha rischiato, permettendo all’amministratore di sistema
di estrarre statistiche o comunque di monitorare il flusso delle informazioni.
Queste specializzazioni saranno affrontate in un prossimo approfondimento.
Abbiamo definito i componenti principale: il Filtro e il bean User. Seguendo
le regole della programmazione ad oggetti occorrerà inserire un
oggetto Binder con il compito di interfacciarsi al repository dei dati
per le operazioni di ricerca, creazione, cancellazione o modifica utente.
La divisone dei compiti tra gli oggetti permette al codice di essere facilmente
adattabile a nuove esigenze modificando pochi componenti: sia che i dati
relativi agli utenti siano memorizzati all'interno di un Database, un
Ldap oppure un file di testo occorrerà modificare esclusivamente
il Factory. Oppure, se il meccanismo di autenticazione varia da http Basic
Authentication a Certificate Client Authentication, basterà intervenire
sul filtro, come vedremo nella parte finale della serie.
Dopo aver implementato la logica di autenticazione, occorrerà sviluppare
una semplice applicazione che consenta la gestione degli utenti.
La prima implementazione
Per ottenere un buon livello di riusabilità del nostro codice,
utilizzeremo alcune “buone pratiche” di programmazione ad
oggetti, in particolare accederemo alle classi tramite le loro interfacce:la
figura 5 descrive le classi per package “it.authproject.bean”,
i loro metodi e le dipendenze;il package include gli oggetti che saranno
utilizzati come java bean e gli oggetti ausiliari come UserBinder
Come primo passo definiamo l’interfaccia User, prevedendo i metodi
per valorizzare e recuperare le seguenti informazioni: username, password,
nome, cognome, data scadenza account , se l’utente è amministratore,
e realizziamo come semplice implementazione la classe .SimpleUser.
Pe accedere al repository dei dati, utilizzeremo le implementazione dell’interfaccia
UserBinder che espone i seguenti metodi:
• User getUser(String[] credenziali): restituisce un oggetto User
se le credenziali sono esatte altrimenti null;
• public boolean saveUser(User user): effetua la scrittura/sovrascrittura
nel repository;
• public boolean deleteUser(User user) cancella l’utente dal
repository;
• public User[] getAll(); restituiscetutti i utenti censiti;
I metodi interessati dalla scrittura sul repository, restituiscono il
valore booleano “false” in caso di fallimento dell’operazione.
In questa prima implementazione ipotizzeremo di non avere a disposizione
un database o un Ldap dove conservare i dati e quindi questi saranno scritti
su un file di testo che rispecchia le seguenti regole:
• le righe di lunghezza nulla o che iniziano con il carattere “#”
sono ignorate;
• le righe contente i dati hanno il seguente formato:
<username>=<password>;<cognome>;<nome>;<data
scadenza account>;<amministratore[true|false]>;<email>
Per una maggior dettaglio si suggerisce di leggere i commenti del codice
allegato.
Le operazioni di controllo sugli accessi sono inclusi nel package: “it.pa4j.bonuccelli.authproject.filter”
(figura 6) composto da una classe astratta e una per l’implementazione
del meccanismo di autenticazione scelto (in questo caso HttpBase Autentication).
Il filtro, dovendo rispecchiare le specifiche imposte da Sun, implementerà
l’interfaccia javax.servlet.Filter prevedrà tre metodi invocati
dal WebContainer:
• init(FilterConfig conf): invocato all’avvio dell’applicazione,
ha il compito di eseguire le operazioni di inizializzazione. Nella nostra
implementazione recupererà le informazioni necessarie per ottenere
un oggetto di tipo UserBinder.
• destroy():invocato all’arresto dell’applicazione,
è utilizzato per rilasciare in modo controllato le risorse impegnate
• doFilter(ServletRequest req, ServletResponse resp,FilterChain
chain): invocato prima che una richiesta giunga alla risorsa osta sotto
il controllo del filtro, contiene la logica di controllo del filtro.
La logica di controllo è implementata attraverso il seguente codice:
try {
HttpSession sess = request.getSession(false);
User user = (User) sess.getAttribute(USER_ATTRIBUTE);
if (user != null && user.getDataFineValidita().before(new Date())) {
//se l'account è scaduto
//reindirizza alla pagina account scaduto
redirectToExpiredPage(req, resp);
return;
}
if (user != null) //a questo punto l'account è valido
autenticato = true;
}
catch (Exception e) {
autenticato = false;
//lasciato intenzionalmente vuoto
}
if (autenticato) {
//se l'account è valido permetto l'accesso alla risorsa;
chain.doFilter(req, resp);
return;
} else {
try {
doAuthentication(chain, request, response);
} catch (AuthException e1) {
//re - indirizzo ad una pagina di errore
redirectToErrorPage(req,resp);
}
}
Sempre per permettere una buona scalabilità
la classe BaseAutenticationFilter è stata definita astratta demandando
a classi specializzate l’onere di rendere concreti i metodi:
• doAuthentication(FilterChain chain,HttpServletRequest request,HttpServletResponse
response): si occuperà del colloquio con il client per recuperare
le credenziali e, in caso positivo, associare alla request un oggetto
User;
• String getErrorPage() e String getExpiredPage()restituiranno ai
metodi redirectToErrorPage(req,resp) e redirectToExpiredPage(req, resp)
il percorso della risorsa da mostrare all’utente in caso di errore
o di account scaduto
L’implementazione del metodo doAuthentication() per la classe HttpBaseAuthFilter
rispecchia le specifiche del protocollo Http in casi di autenticazione
base, più precisamente ricerca nell’intestazione della richiesta
inviata dal client una riga del tipo:
authorization Basic MIOusername:MIApassword
Se questa riga è presente un oggetto UserBinder verifica la correttezza
delle credenziale inviate (nell’esempio MIOusername e MIApassword)
tramite una chiamata al metodo getUser(); in caso positivo il metodo ritorna
un oggetto User che viene associato alla request, altrimenti (sia in assenza
delle credenziali che in caso di credenziali non valide) viene inviato
al client una response che contiene le righe:
Status 401
WWW-Authenticate Basic
Il browser istallato sul client è in grado di interpretare queste
righe facendo apparire all’utente una finestra di input dove digitare
login e password. Quando l’utente preme il bottone di invio il browser
replica la richiesta originale aggiungendo nell’header le credenziali
secondo il formato previsto dallo standard Http e utilizzato dalla nostra
classe.
La configurazione e la predisposizione per
il deploy.
I parametri di configurazione necessari sono il percorso del file user.txt
(il quale conterrà l’elenco degli utenti abilitati) e l’elenco
delle risorse da porre sotto accesso controllato.
Per provare il funzionamento del filtro senza intervenire su un’applicazione
già esistente è possibile scaricare dal sito ftp di pa4j
il pacchetto pa4jAuthFilter.war che contiene una semplice dimostrazione.
Oltre al filtro il pacchetto contiene la prima applicazione di gestione
utenti che nel prossimo articolo vedremo in dettaglio.
La pubblicazione della demo in Apache Tomcat (http://jakarta.apache.org/tomcat/)
consiste nel copiare il file pa4jAuthFilter.war all’interno della
directory <tomcat-home>/webapps/.
In caso di una istallazione standard, il server risponde alla porta 8080;
dopo aver avviato il WebContainer è possibile testare il funzionamento
del filtro tentando di accedere alla risorsa con un browser all’indirizzo
http://localhost:8080/pa4jAuthFilter/ direttamente dal server.
Per inserire il filro all’interno di un applicazione già
esistente occorre modificare quindi il file di configurazione della nostra
applicazione (/WEB-INF/web.xml) inserendo i percorsi relativi delle risorse
protette e il path del file con funzione di repository dei dati, compilare
il codice sorgente (disponibile sul sito ftp di pa4j) ed copiare i file
“.class” all’interno della directory WEB-INF/classes/.
La struttura del file web.xml è definita per standard, occorrerà
inserire:
• Tra dichiarazioni dei parametri di contesto il riferimento al
file user.txt (che avremo già copiato sul filesystem).
<context-param>
<param-name>USERFILE</param-name>
<param-value>/pa4j/users.txt</param-value>
</context-param>
• La dichiarazione del filtro, della classe che lo implementa e
dei parametri d’inizializzazione:
<filter>
<filter-name>HttpBaseAutenticationFilter</filter-name>
<filter-class>it.pa4j.bonuccelli.authproject.filter.HttpBaseAuthFilter</filter-class>
<init-param>
<param-name>ERRORPAGE</param-name>
<param-value>loginerror.jsp</param-value>
</init-param>
<init-param>
<param-name>EXPIREDPAGE</param-name>
<param-value>accountexpired.jsp</param-value>
</init-param>
</filter>
Per ultimo l’elenco dei percorsi da porre sotto il controllo del
filtro. Per aggiungere altri percorsi occorre ripetere più volte
il tag <filter-mapping> modificando l’url-pattern.
Il carattere “*” (star) indica qualunque percorso. Nell'applicazione
dimostrativa abbiamo ipotizzato che le risorse protette fossero sotto
il percorso relativo “/protected/”
<filter-mapping>
<filter-name>HttpBaseAutenticationFilter</filter-name>
<url-pattern>/protected/*</url-pattern>
</filter-mapping>
Quando l’utente cercherà di accedere ad
una risorsa protetta il browser presenterà una maschera di inserimento
dati.
Nel prossimo articolo vedremo come implementare lo sviluppo del tool d’amministrazione,
come personalizzare le pagine aggiungendo un messaggio di benvenuto, la
realizzazione un meccanismo di registrazione automatica, e altro ancora.