JAVA SERVLET: I filtri - the

Transcript

JAVA SERVLET: I filtri - the
JAVA SERVLET: I filtri
INTRODUZIONE
Quest’oggi vedremo una particolarità delle servlet che è stata introdotta nelle servlet API versione 2.3.
I filtri sono un potente meccanismo che è in grado di fare quello che prima veniva fatto usando il “servlet chaining”
(concatenamento di servlet o catene di servlet) e molto di più.
Non ci fermeremo a presentare solo le caratteristiche dei filtri ma vedremo anche un esempio pratico di utilizzo.
Per poter capire a fondo quello che spiegherò in questo tutorial è importante avere una buona conoscenza di Java e
conoscere almeno il meccanismo generale di funzionamento delle servlet.
DESCRIZIONE
Quando il programmatore vuole creare un nuovo filtro da poter utilizzare all’interno della propria applicazione web,
non deve far altro che creare una classe che implementi l’interfaccia javax.servlet.Filter. Vedremo poi quali metodi
bisogna implementare, nel frattempo diamo una breve descrizione del meccanismo dei filtri.
Come suggerisce il nome stesso lo scopo di queste classi è quello di “filtrare” dei contenuti. E su quali contenuti
potrebbero mai agire? Ma su richiesta e risposta naturalmente… o meglio sugli oggetti che implementano le interfacce
ServletRequest e ServletResponse.
Definire un filtro una servlet è errato per una serie di motivi:
- non creano realmente una risposta al contrario delle servlet
- hanno metodi differenti rispetto alle servlet, ad esempio il metodo doFilter()
- non hanno i tipici metodi doGet() e doPost() delle servlet normali
Lo scopo dei filtri è quello di effettuare operazioni di preprocessing delle richieste e postprocessing delle risposte:
intervengono quindi prima che una richiesta raggiunga una servlet o appena dopo che una risposta esca da una servlet.
Ecco un esempio di servlet che preprocessa la richiesta:
Schema di funzionamento:
1) Il client invia una richiesta al server e il filtro la intercetta
2) Il filtro preprocessa la richiesta, raccogliendo eventuali informazioni
3) Il filtro richiama il metodo chain.doFilter (lo vedremo meglio dopo) per invocare la prossima servlet o il
webcomponent
4) Il webcomponent invocato genera la risposta.
Ecco un esempio invece di servlet che postprocessa la risposta:
1
Schema di funzionamento:
1) Il client invia una richiesta al server e il filtro la intercetta
2) Il filtro richiama il metodo chain.doFilter
3) Il webcomponent corrispondente risponde, generando la risposta.
4) La risposta viene intercetta dal filtro e viene processata
5) La risposta eventualmente modificata viene restituita al client
Come potete vedere la possibilità di utilizzo dei filtri è amplissima…basta che pensiate a tutte le cose che si potrebbero
fare potendo modificare una risposta http o potendo ricavare informazioni da una richiesta http.
In particolare l’applicazione pratica che noi vedremo sarà un esempio di filtro di autenticazione, ossia il nostro filtro
fornirà un forma di protezione da tentativi di accesso non autorizzato a certe risorse sul webserver.
Altri esempi di possibile filtro: una filtro che effettua del logging con informazioni riguardo al client oppure ai tempi di
elaborazione delle pagine oppure un filtro di compressione dati.
Prima di andare a vedere l’applicazione pratica tentiamo di dare un sguardo più da vicino ai filtri.
“DENTRO” AI FILTRI
Un filtro per essere considerato tale deve implementare l’interfaccia javax.servlet.Filter e di conseguenza i tre metodi ad
essa associati:
 void init(FilterConfig config): in un certo senso possiamo paragonarlo al metodo init() delle servlet. Questo
metodo viene invocato dal web container (tipicamente si usa Tomcat come servlet engine) prima che il filtro
possa cominciare qualsiasi tipo di operazione di filtraggio e di conseguenza viene chiamato prima del metodo
doFilter. Nel metodo init() del filtro è possibile svolgere eventuali inizializzazioni di variabili o parametri
d’ambiente.
 void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) : questo metodo esegue
tutte le operazioni necessarie. In pratica viene invocato ogni qualvolta un client fa una richiesta e quindi si
hanno una coppia request/response.
 void destroy(): come già detto per il metodo init anche qui possiamo trovare una corrispondenza col metodo
destroy delle servlet. Questo metodo viene invocato alla fine del ciclo di vita del filtro ossia quando il servlet
container decide di rendere inutilizzabile l’istanza del filtro o quando non ci sono più thread in esecuzione sul
metodo doFilter. In questo metodo può essere inserito anche del codice per liberare risorse precedentemente
impegnate e ora non più utilizzate…per fare un esempio banale delle connessioni ad un database o dei file
ancora aperti. Insomma il metodo destroy viene invocato in fase di chiusura del servlet container o della
webapplication.
Due parole in più sul metodo doFilter() il più importante/interessante di un filtro.
Come si può notare dalla firma del metodo abbiamo tre argomenti.
1. ServletRequest request: è un interfaccia che definisce un oggetto generico di tipo richiesta. Tipicamente viene
passata una richiesta http di conseguenza un oggetto che implementi l’interfaccia
javax.servlet.HttpServletRequest. Quest’ultima è una sottointerfaccia che estende l’interfaccia
javax.servlet.ServletRequest: è per così dire una specializzazione dell’oggetto generale di richiesta per il
protocollo http.
2. ServletResponse response: vale lo stesso discorso fatto sopra per la request, solo che qui parliamo di
interfaccia javax.servlet.ServletResponse e di sottointerfaccia javax.servlet.HttpServletResponse.
3. FilterChain chain: qui parliamo dell’interfaccia javax.servlet.FilterChain. In questo caso abbiamo a che fare
con un oggetto creato appositamente dal servlet container per fare in modo che il programmatore possa
facilmente passare il controllo sugli oggetti richiesta e risposta al prossimo filtro nella catena di filtri.
Il bello dei filtri è quello di poter tranquillamente costruire una catena di filtri, ognuno dei quali esegue una
parte di lavoro. Ad esempio: un filtro che fa del logging, seguito da un filtro che raccoglie informazioni sulla
richiesta da parte del client (browser, os, provenienza,etc.) e infine un filtro di autenticazione che si preoccupa
di controllare che il client possa effettivamente accedere alla risorsa richiesta.
Quando un filtro ha terminato il proprio lavoro di preprocessing può passare il controllo al filtro successivo
semplicemente chiamando il metodo FilterChain.doFilter(). Nel caso non ci siano più filtri allora viene
chiamata la risorsa specificata dalla richiesta.
NOTA: tutti e tre gli argomenti sono interfacce, quindi non classi e vere istanze di oggetti. E’ compito del servlet
container passare degli oggetti corrispondenti (costruiti ad “arte”) che implementino le relative interfacce. Se si vanno
infatti a vedere le servlet API non si troveranno classi che implementino un vero oggetto richiesta o risposta. Le classi
wrapper sono un’eccezione, ma in realtà esse non sono veri oggetti richiesta o risposta.
2
CONFIGURAZIONE DEI FILTRI
Poiché i filtri possiamo considerarli alla stregua delle servlet, anche loro devono essere configurati in maniera
opportuna all’interno del file web.xml per funzionare correttamente nella web application.
Abbiamo già accennato all’inizio di come vedremo un esempio pratico di filtro di autenticazione. Il nostro filtro si
chiamerà LoginFilter: vediamo quindi come configurarlo.
Parte di un ipotetico file web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>it.webtraffic.filters.LoginFilter</filter-class>
<init-param>
<param-name>loginPage</param-name>
<param-value>/index.htm</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/statistiche/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/admin/*</url-pattern>
</filter-mapping>
………
</web-app>
Osservazioni:
1. Le dichiarazioni dei filtri vanno fatte prima delle dichiarazioni delle servlet.
2. Le dichiarazioni dei filtri vanno fatte nell’ordine che con cui si vuole costruire la catena di filtri.
3. I tag <filter></filter> permettono di dichiarare i filtri.
4. I tag <filter-name></filter-name> permettono di definire il nome-identificatore del filtro.
5. I tag <filter-class></filter-class> permettono di indicare la classe filtro.
6. I tag <init-param></init-param> e gli altri tag interni servono per specificare parametri di inizializzazione del
filtro (come avviene per le servlet) che possono essere recuperate ad esempio nel metodo init().
7. I tag <filter-mapping></filter-mapping> e gli altri tag interni servono per specificare quando un filtro deve
essere invocato ossia in base a quali richieste. In particolare si specifica il nome del filtro che deve essere
invocato e l’url-pattern relativo. Nel nostro caso il filtro entra in azione quando arrivano richieste del tipo
/admin/* o /statistiche/*. Per fare in maniera che il filtro entri in azione sempre si può specificare il pattern /*.
Per farlo funzionare solo con particolari estensioni di file basta racchiudere tra i tag <url-pattern></url-pattern>
l’estensione dei file: ad esempio *.php o *.htm.
8. Possono essere specificati un numero arbitrario di mapping per i filtri (così come per le servlet).
Bene, dopo questa breve ma esaustiva introduzione ai filtri vediamo un’applicazione reale, in maniera da renderci
contro delle potenzialità di questo meccanismo.
IMPLEMENTAZIONE DI UN FILTRO DI AUTENTICAZIONE
Progettare un filtro di autenticazione significa progettare un meccanismo che fornisca una forma di protezione di dati
considerati sensibili e ai quali si vuole che accedano soltanto particolari categorie di utenti.
Nella fattispecie (anche facendo riferimento all’estratto di web.xml visto sopra) vogliamo che soltanto gli “utenti
privilegiati” possano accedere a certi percorsi.
Il codice java che vedrete qui sotto è stato scritto dal sottoscritto nell’ambito di un progetto di laboratorio di reti che
prevedeva lo sviluppo di un sistema di statistiche per l’accesso ai siti web sul modello di shynistat. Il progetto è stato
svolto assieme ad un altro mio collega all’università, e mentre lui si occupava della parte di statistiche (elaborazione dei
dati raccolti e generazione grafici) io mi sono occupato tra l’altro anche del meccanismo di login/autenticazione.
3
Il codice java per quanto possibile rispecchierà l’originale utilizzato nell’applicazione web completa, e quando
necessario aggiungerò commenti per aiutare la comprensione del funzionamento del filtro.
/*
* LoginFilter.java
* Classe filtro che controlla i permessi di un client per l’accesso
* a determinati percorsi sul webserver.
* Parte integrante dell’applicazione web WEB TRAFFIC ANALYZER.
*/
package it.webtraffic.filters;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
import it.webtraffic.beans.Admin;
public class LoginFilter implements Filter {
private String loginPage;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//faccio un downcast della richiesta e della risposta.
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
//recupero la sessione del client che ha effettuato la richiesta
HttpSession session = req.getSession();
//tento di recuperare il bean Admin:
//si tratta di un bean contenente le informazioni riguardanti l’amministratore
Admin administrator = (Admin) session.getAttribute("admin");
if (administrator == null) { //un utente generico ha tentato di accedere ai percorsi protetti
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<head>");
out.println("<meta http-equiv=\"refresh\" content=\"2; URL=" + loginPage +"\">");
out.println("<div align='center'><H3>EFFETTUARE IL LOGIN PRIMA DI ACCEDERE AL PERCORSO
SPECIFICATO....<br><br>");
out.println("Redirezione alla pagina di login in corso</H3></div>");
out.close();
}
//ora controllo se si tratta di un amministratore semplice o avanzato.
//gli amministratori avanzati hanno la possibilità anche di accedere anche ai percorsi di tipo /admin/*
//il metodo getIfGod() del bean ci consente di capire se si tratta di admin semplice (false) o avanzato (true)
else if (req.getRequestURI().indexOf("/admin") != -1 && !administrator.getIfGod()) {
//si tratta di un amministratore semplice quindi segnalo l’errore.
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<head>");
out.println("<meta http-equiv=\"refresh\" content=\"2; URL=/statistiche/statistiche/home.jsp\">");
out.println("<div align='center'><H3>IMPOSSIBILE ACCEDERE: NON SEI GOD ADMIN....<br><br>");
out.println("Redirezione alla pagina di principale in corso</H3></div>");
out.close();
}
else {
//nel caso le credenziali siano verificate inoltro richiesta e risposta all’ipotetico prossimo filtro.
chain.doFilter(req, res);
}
}
/** Metodo destroy del filtro. */
public void destroy() {
}
}
/** Metodo init del filtro. */
public void init(FilterConfig filterConfig) throws ServletException{
//tento di recuperare il parametro loginPage che dovrebbe essere specificato nel file web.xml
loginPage = "/statistiche" + filterConfig.getInitParameter("loginPage");
//se non ci riesco segnalo l’errore
if (loginPage == null)
throw new ServletException("Init Parameter loginPage mancante: modificare il file web.xml");
}
4
Come si può vedere il meccanismo di autenticazione implementato è piuttosto semplice ma funzionale e serve a
illustrare quanto spiegato sopra.
In sostanza il tutto si basa sulla presenza o meno di un attributo di sessione, un bean Admin che contiene informazioni
sull’eventuale amministratore. Un metodo getIfGod() che ritorna un boolean inoltre ci permette di distinguere tra
SIMPLE (semplice) e GOD (avanzato) ADMIN.
Il grosso del lavoro viene per l’appunto svolto all’interno del metodo doFilter(). Il metodo destroy() risulta vuoto visto
che non c’è la necessità di liberare risorse, mentre nel metodo init() abbiamo tentato di recuperare il parametro di
inizializzazione “loginPage”.
CONCLUSIONI
Questo è solo un esempio di come impiegare un semplice/potente meccanismo quali i filtri all’interno di una web
application, ora a voi divertirvi a crearne di vostri.
Spero che quello che ho spiegato oggi possa avervi aiutato a scoprire un lato delle servlet che non avevate ancora
analizzato. Alla prossima allora!
RINGRAZIAMENTI: un particolare ringraziamento a Massimiliano Barletta, mio collega all’unive col quale ho
condiviso l’“avventura” di creare il WEB TRAFFIC ANALYZER. Ci siamo aiutati a vicenda nella realizzazione del
progetto che alla fine ci ha permesso di imparare molto sulle tecnologie servlet e jsp, e ci siamo incoraggiati
moralmente a vicenda quando le cose sembravano non andare per il verso giusto… quindi GRAZIE E AL PROSSIMO
PROGETTO!
NOTE AL DOCUMENTO
Fonti bibliografiche:
- Jason Hunter e William Crawford “JAVA SERVLET PROGRAMMING” seconda edizione aggiornata.
- WEB TRAFFIC ANALYZER Progetto di Laboratorio di Reti anno accademico 2002/2003
- Enterprise JavaTM Technologies Tech Tips for September 19, 2002
LINK: http://developer.java.sun.com/developer/EJTechTips/2002/tt0919.html
Autore del documento: sego
Permessi sul documento: l’uso di questo documento è a puro scopo informativo e non a fini di lucro. Il contenuto del
documento può essere usato liberamente. Nel caso utilizziate o pubblichiate sul vostro sito questo documento siete
pregati di lasciarlo integro o di lasciare almeno un riferimento all’autore originale.
Informazioni di carattere generale: qualsiasi suggerimento o critica costruttiva è ben accetto. Se avete idee per
migliorare il documento inviatemi pure una mail. La segnalazione di errori o disattenzioni è altresì ben accetta.
sego(owner/webmaster of the-skulls.com)
indirizzo di posta elettronica: [email protected]
sito web di riferimento: http://www.the-skulls.com
5