Acegi Security System offre le funzionalità tipiche di gestione della sicurezza. Essendo costituito da un insieme di componenti configurabili in Spring, è il candidato ideale per applicazioni basate su questo framework.
Introduzione
Gestire gli aspetti di security di una applicazione significa garantire che il sistema sia in grado di valutare le informazioni di accesso fornite dall’utente, e in base ad esse, permettere o no l’ingresso. Inoltre deve essere in grado di discernere da queste stesse informazioni le abilitazioni dell’utente all’accesso delle varie risorse del sistema. Le problematiche di sicurezza posseggono degli aspetti standard che nella loro generalità costituiscono una struttura in qualche modo “esterna” all’applicazione. È un sicuro vantaggio utilizzare un framework che in qualche modo codifichi questi standard e li gestisca in maniera appropriata, evitando di dover reinventare soluzioni a problemi già studiati e risolti positivamente dalla comunità degli sviluppatori.
Acegi Security System è un framework che utilizza le caratteristiche di Injection of Control di Spring e fornisce un insieme di beanche risolvono tutte le problematiche più comuni, compreso l’interfacciamento verso un sistema di Single Sign On come CAS. Questo articolo fornisce una breve panoramica del framework Acegi: nel prossimo paragrafo si espone la struttura generale passando successivamente un po’ più nel dettaglio dei principali componenti.
Struttura generale
La figura 1 mostra la struttura generale di Acegi Security System:
Figura 1 – Schema generale del sistema Acegi
Acegi si basa su un insieme di filtri Servlet per implementare la messa in sicurezza di un’applicazione web. I filtri sono uno standard della tecnologia servlet e permettono di inserirsi nel ciclo di vita della richiesta web. I filtri di Acegi delegano a specifici componenti l’implementazione dei diversi aspetti della security. Tra tali componenti quelli principali sono i security interceptors che si affidano agli altri due componenti mostrati in figura: gli authentication managers e gli access decision managers. Il primo gestisce la fase di autenticazione che è quella preliminare che precede l’accesso effettivo all’applicazione. Il sistema deve essere in grado di verificare l’identità dell’utente che cerca di effettuare il login al sistema e lo fa tramite le sue credenziali di accesso, normalmente la password. Superata questa prima fase deve verificare, attraverso le informazioni a corredo delle credenziali sopra riportate, a quali risorse l’utente è abilitato ad accedere, e qui entrano in gioco gli access decision managers.
Autenticazione
L’interfaccia seguente è l’astrazione principale che identifica il processo di autenticazione:
public interface AuthenticationManager {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
Ha un unico metodo authenticate che prende in ingresso un oggetto di tipo Authentication che contiene le informazioni associate all’utente (nome utente e credenziali). Il metodo authenticate lancia un’eccezione di tipo AuthenticationException nel caso di autenticazione fallita, mentre nel caso contrario restituisce lo stesso oggetto Authentication contenente, oltre al nome utente e alle credenziali, le eventuali informazioni aggiuntive che ne certificano i privilegi di accesso.
Acegi fornisce delle implementazioni di tale interfaccia per gli scenari più comuni. La figura 2 mostra un elenco di queste implementazioni.
Figura 2 – Implementazione di Provider Manager disponibili.
La classe in cima alla gerarchia è il ProviderManager, che implementa l’interfaccia AuthenticationManager. Le diverse sottoclassi implementano gli specifici scenari. Nella situazione più comune le informazioni di autenticazione, così come quelle di controllo dell’accesso, devono essere recuperate da un database, ma può esserci anche il caso di interfacciamento con uno strato JAAS oppure con un servizio di Single Sign On come CAS.
Le due implementazioni DaoAuthenticationProvider e PasswordDaoAuthenticatioProvider coprono due situazioni comuni nel caso di interfacciamento con una generica sorgente dati, che utilizzano un oggetto DAO per leggere le informazioni. Nel primo caso l’operazione di controllo delle credenziali di accesso viene fatta dallo stesso componente DaoAuthenticationProvider, mentre nel secondo questa operazione viene delegata all’oggetto DAO e in definitiva al servizio di memorizzazione esterno al quale si accede; questo è il caso per esempio di un servizio LDAP.
Il DAO deve essere un’implementazione della AuthenticationDao e sarà fornito al componente ProviderManager tramite la configurazione di Spring, come di consueto. Il componente DAO stesso deve essere configurato in Spring e può essere personalizzato in modo da tener conto delle specificità del sistema. Per esempio, per default, il JdbcDaoImpl, che è una specifica implementazione della AuthenticationDao, si aspetta di trovare una tabella users e una tabella authorities; nel caso il proprio sistema non abbia tabelle con lo stesso nome e la stessa struttura di queste ultime è possibile personalizzare il processo di recupero delle informazioni tramite delle proprietà aggiuntive fornite al bean in configurazione.
Controllo dell’accesso
La fase di autenticazione è solo la fase preliminare del processo di sicurezza di un applicativo. Una volta che il sistema identifica l’utente deve decidere in base alle informazioni associate se esso ha i privilegi necessari ad accedere a determinate risorse. L’interfaccia principale che caratterizza il processo di controllo di accesso è la seguente:
public interface AccessDecisionManager {
public void decide(Authentication authentication, Object object,
ConfigAttributeDefinition config)
throws AccessDeniedException;
public boolean supports(ConfigAttribute attribute);
public boolean supports(Class clazz);
}
Il metodo principale è il metodo “decide” che, in caso di responso negativo, lancia una AccessDeniedException. Il metodo “supports” prende in ingresso in una versione l’insieme di attributi di sicurezza di una risorsa e nell’altro il tipo della classe, e restituisce un booleano che certifica se l’AccessDecisionManager è in grado di effettuare delle decisioni di accesso sulla risorsa in questione. L’AccessDecisionManager compare in tre implementazioni principali, che però delegano la decisione effettiva a un insieme di componenti chiamati “voters”. Le tre implementazioni sono le seguenti:
- net.sf.acegisecurity.vote.AffirmativeBased: permette l’accesso se almeno uno dei voters garantisce l’accesso
- net.sf.acegisecurity.vote.ConsensusBased: permette l’accesso se l’unanimità dei voters garantiscono l’accesso
- net.sf.acegisecurity.vote.UnanimousBased: permette l’accesso solo se nessun voter proibisce l’accesso.
Ed ecco un esempio di configurazione di un AccessDecisionManager di tipo UnanimousBased:
class="net.sf.acegisecurity.vote.UnanimousBased">
La proprietà “decisionVoters” contiene uno o più riferimenti a oggetti “voter”. Gli oggetti voter implementano l’interfaccia AccessDecisionVoter di seguito riportata:
public interface AccessDecisionVoter {
public static final int ACCESS_GRANTED = 1;
public static final int ACCESS_ABSTAIN = 0;
public static final int ACCESS_DENIED = -1;
public boolean supports(ConfigAttribute attribute);
public boolean supports(Class clazz);
public int vote(Authentication authentication, Object object,
ConfigAttributeDefinition config);
}
Questa classe è quasi identica alla AccessDecisionManager e differisce solo per il fatto che il metodo “decide” è sostituito da un metodo “vote” che restituisce un intero codificato dalle costanti definite nell’interfaccia stessa.
Esiste per esempio un’implementazione di AccessDecisionVoter, la net.sf.acegisecurity.vote.RoleVoter, che esprime una decisione sull’accesso a una risorsa se la risorsa stessa è associata a informazioni codificate con un prefisso “ROLE_”. È possibile anche modificarne le caratteristiche in modo che il prefisso in questione sia diverso. Il roleVoter vota ACCESS_ABSTAIN solo quando non trova un’informazione prefissata con “ROLE_”.
Mettere in sicurezza le applicazioni web
Tutto il processo di messa in sicurezza dell’applicazione da parte di Acegi passa attraverso un insieme di filtri Servlet:
- Channel-processing filter: assicura che la richiesta passi attraverso un canale sicuro (HTTPS).
- Authentication-processing filter: accetta le richieste e le inoltra a un authentication manager.
- CAS-processing filter: gestisce l’autenticazione tramite il controllo del “ticket” del servizio di Single Sign On CAS associato alle richiesta.
- HTTP Basic authorization filter: gestisce un processo di autenticazione del tipo HTTP Basic authentication
- Integration filter: gestisce la memorizzazione delle informazioni di autenticazione tra le richieste (normalmente nella sessione HTTP).
- Security enforcement filter: assicura che un utente sia autenticato e che abbia i privilegi necessari ad accedere alla risorsa richiesta.
Perche‘ tali filtri possano utilizzare tutte le caratteristiche di Injection of Control di Spring devono implementare l’interfaccia net.sf.acegisecurity.util.FilterToBeanProxy. Ecco un esempio di definizione di un tale filtro:
myFilter net.sf.acegisecurity.util.
FilterToBeanProxytargetClass
myFilterBean
In tal modo il punto di ingresso è il filtro myFilter che però delega tutte le sue funzionalità a un oggetto della classe targetClass, che può essere configurato come un qualunque bean di Spring. Se per esempio vogliamo configurare il SecurityEnforcementFilter, che è quello che implementa i concetti definiti nei paragrafi precedenti (autenticazione e controllo di accesso), dobbiamo inserire nel web.xml una definizione del tipo sopra riportato, con un parametro “targetClass” che in questo caso ha come valore un bean “securityEnforcementFilter” definito nel file di configurazione di Spring, come di seguito:
Tale filtro utilizza i due bean securityInterceptor e authenticationEntryPoint. L’ultimo si occupa principalmente di redirigere la richiesta a una maschera di login, quando necessario. Per esempio un AuthenticationProcessingFilterEntryPoint redirige l’utente verso un form HTML.
Il SecurityInterceptor invece implementa la logica di autenticazione e di controllo di accesso attraverso un AuthenticatioManager e un AccessDecisionManager. Ecco un esempio di configurazione di un SecurityInterceptor.
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
A/admin/.* =ROLE_ADMIN
A/oper/.* =ROLE_OPER
La proprietà objectDefinitionSource permette di definire l’associazione tra particolari URL e i privilegi necessari per accedere alle risorse relative a tali URL. In tal modo l’AccessDecisionManager è in grado di confrontare tali informazioni con quelle associate all’utente che cerca di accedere alle risorse.
Conclusioni
Abbiamo visto in questo articolo come il sistema Acegi permetta di gestire in modo controllato gli aspetti di sicurezza della propria applicazione. I meccanismi dichiarativi forniti dalla sua integrazione stretta con Spring permettono di gestire al meglio l’integrazione con sistemi di autenticazione come LDAP o di Single Sign On come CAS.
Riferimenti
[1] “Spring Reference” 2.0
http://www.springframework.org/
[2] Craig Walls – Ryan Breidenbach, “Spring in Action”, Manning, 2005