Autenticazione di servizi REST con Spring Security

In questo post aggiungeremo il controllo accessi ai metodi esposti nell’esempio WebService RESTful con Jersey visto in precedenza. Le modifiche rispetto al progetto originale sono abbastanza semplici per cui in questo articolo analizzeremo solamente il nuovo codice aggiunto.

Premetto che esistono diversi modi di fornire un’autenticazione sicura ai nostri servizi: potremmo gestire i permessi a livello di servlet container, o lasciare tutto in carico a Jersey che offre il supporto a OAuth protocollo che consente alle applicazioni di accedere alle risorse protette da un servizio Web, come spiega questa guida.

In questo post useremo un’ulteriore soluzione: integreremo Spring Security per la gestione delle autenticazioni e delle autorizzazioni. Spring Security è un framework per il controllo accessi potente e altamente personalizzabile, ed è attualmente lo standard per la protezione di applicazioni basate su Spring.

Le modifiche al web service originale

Passiamo ora al nostro esempio. Come prima cosa creiamo un nuovo progetto Maven che chiamiamo secureJersey in cui replichiamo tutte le parti sviluppate nel progetto testJersey. Mantenere i due progetti distinti ci aiuta ad analizzare meglio le differenze prima e dopo la modifica.
All’interno del pom.xml aggiungiamo alcune delle dipendenze del framework Spring, e le dipendenze del framework Spring Security come riportato di seguito.

    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>3.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>3.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>3.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>3.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>3.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>3.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.2.2</version>
    </dependency>

Il passo successivo è la modifica del file web.xml.

  <listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  <listener>
    <listener-class>
       org.springframework.web.context.request.RequestContextListener
    </listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring-security.xml</param-value>
  </context-param>
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>
       org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

Analizziamo in breve le modifiche al file:

  • L’aggiunta del listener ContextLoaderListener ci permette di integrare Spring nella web application.
  • RequestContextListener serve per permettere di richiamare un sessionBean all’interno di SpringSecurity.
  • La classe org.springframework.web.filter.DelegatingFilterProxy è il filtro di Spring che permette di intercettare le chiamate al nostro servizio.
  • Il file spring-security.xml non è altro che il principale file di configurazione di Spring, ovvero l’ApplicationContext, definito ad hoc per Spring Security. Vediamo di seguito la sua struttura.
<context:component-scan base-package="it.test.rest.webservice" />
 <security:http create-session="stateless" use-expressions="true">
  <security:intercept-url pattern="/rest/persona/**" access="hasRole('ROLE_ADMIN')"/>
  <security:http-basic />
 </security:http>

 <security:authentication-manager alias="authenticationManager">
  <security:authentication-provider>
   <security:user-service>
    <security:user authorities="ROLE_ADMIN" name="user1" password="password1" />
   </security:user-service>
  </security:authentication-provider>
 </security:authentication-manager>
</beans>

All’interno del tag http definiamo l’attributo create-session="stateless". Questo significa che ad ogni richiesta da parte del client un’istanza del session bean di tipo stateless viene recuperata dal pool e assegnata al client, quando la richiesta si conclude, l’istanza torna al pool per un successivo riutilizzo.
Inoltre qui vengono definiti i path a cui applicare la sicurezza e i profili a cui in seguito assoceremo login e password.
I tag authentication-manager e authentication-provider definiscono le regole con cui un utente viene riconosciuto dal sistema. La lista dei provider che gestiscono il meccanismo di autenticazione è contenuta nel tag authentication-manager. Ogni tag authentication-provider ha uno user-service, cioè un meccanismo che definisce qual è il profilo dell’utente e le chiavi di accesso all’interno dell’applicazione.

Con questo abbiamo terminato le modifiche, ora se lanciamo il WebService e richiamiamo uno dei servizi che risiedono sotto il path protetto, il browser ci richiede di inserire le nostre credenziali di accesso.

login0

Chiamare i servizi protetti lato client

Come certamente ricorderete, avevamo definito una web application, che avevamo chiamato jsfJersey, per testare la chiamata ai servizi di testJersey.
Proviamo a chiamare da jsfJersey il nuovo webservice modificando solamente il puntamento alla nuova URL all’interno della classe PersService:

	private static URI getBaseURI() {
		return UriBuilder.fromUri("http://localhost:8080/secureJersey").build();
	}

Lanciando l’applicazione riceveremo un errore di questo tipo:

GET http://localhost:8080/secureJersey/rest/persona returned a response status of 401 Unauthorized

Non siamo autorizzati! Per cui il webservice non trasmette i dati al chiamante se prima non forniamo le credenziali di accesso. A questo punto inseriamo la riga di codice riportata di seguito all’interno del metodo che richiama il servizio protetto:

client.addFilter(new HTTPBasicAuthFilter("user1", "password1"));

La classe HTTPBasicAuthFilter è fornita da Jersey per il passaggio delle credenziali di autenticazione HTTP basic. user1 e password1 sono le chiavi di accesso che abbiamo precedentemente definito all’interno del file spring-security.xml.
Se proviamo a rilanciare l’applicazione ora tutto funziona correttamente.

Conclusioni

In questo post abbiamo reso sicuro il nostro web service con pochi settaggi e senza scrivere una riga di codice Java. Ovviamente questo è un esempio base, Spring Security è un framework avanzato con molte altre funzionalità come l’inserimento di più profili utente con abilitazioni a pattern diversi, la crittografia delle password e molto altro. Per ulteriori approfondimenti vi rimando alla pagina ufficiale.

L’esempio proposto in questo articolo è scaricabile da GitHub.

Mauro Cognolato

Mi chiamo Mauro Cognolato laureato in Statistica e Gestione delle Imprese presso l'Università di Padova, e da 14 anni impiegato come consulente informatico, analista, programmatore nel settore bancario. Ho iniziato la mia attività come sviluppatore su sistemi Mainframe IBM: Cobol, CICS, DB2. Attualmente lavoro in ambiente dipartimentale sviluppando applicazioni web J2EE e processi ETL IBM Datastage su database Oracle e MS SQL Server. Inoltre sviluppo siti web su sistemi LAMP con HTML5 e JQuery.

  • Ottimo articolo. Quando si cerca di trasferire un concetto come al solito l’esemplificazione dei meccanismi di base risulta efficace, o almeno è così nel mio caso.

    Che altro dire?

    Grazie e… buon Natale a te e a tutto lo staff di questo spettacolare sito che considero la mia personale scoperta dell’anno.

    Continuate così 😉

    • MauroCognolato

      Grazie dei complimenti Alessandro! Auguro un sereno Natale anche a te.

    • Giampaolo Trapasso

      Grazie Alessandro, buone feste anche a te!

  • Pingback: JSON Web Token: JAAS stateless authentication per Tomcat :: CoseNonJaviste()