19. Proteggere l'accesso a un servizio web con Spring Security
19.1. Il ruolo di Spring Security in un’applicazione web
Inquadriamo Spring Security nello sviluppo di un’applicazione web. Nella maggior parte dei casi, questa sarà costruita su un’architettura a più livelli come la seguente:
![]() |
- il livello [Spring Security] concede l’accesso al livello [web] solo agli utenti autorizzati.
19.2. Un tutorial su Spring Security
Importiamo nuovamente una guida Spring seguendo i passaggi da 1 a 3 riportati di seguito:
![]() |
![]() |
Il progetto è composto dai seguenti elementi:
- nella cartella [templates] si trovano le pagine HTML del progetto;
- [Application]: è la classe eseguibile del progetto;
- [MvcConfig]: è la classe di configurazione di Spring MVC;
- [WebSecurityConfig]: è la classe di configurazione di Spring Security;
19.2.1. Configurazione Maven
Il progetto [3] è un progetto Maven. Esaminiamo il suo file [pom.xml] per conoscere le sue dipendenze:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-securing-web</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- tag::security[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- end::security[] -->
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- righe 10-14: il progetto è un progetto Spring Boot;
- righe 17-20: dipendenza dal framework [Thymeleaf];
- righe 22-25: dipendenza dal framework Spring Security;
19.2.2. Le viste Thymeleaf
![]() |
La vista [home.html] è la seguente:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
Click <a th:href="@{/hello}">here</a> to see a greeting.
</p>
</body>
</html>
- riga 12: l'attributo [th:href="@{/hello}"] genererà l'attributo [href] del tag <a>. Il valore [@{/hello}] genererà il percorso [<context>/hello], dove [context] è il contesto dell'applicazione web;
Il codice HTML generato è il seguente:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
Click
<a href="/hello">here</a>
to see a greeting.
</p>
</body>
</html>
La vista [hello.html] è la seguente:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
</body>
</html>
- riga 9: l'attributo [th:inline="text"] genererà il testo del tag <h1>. Questo testo contiene un'espressione $ che deve essere valutata. L'elemento [[${#httpServletRequest.remoteUser}]] è il valore dell'attributo [RemoteUser] della richiesta HTTP corrente. Si tratta del nome dell'utente connesso;
- riga 10: un modulo HTML. L’attributo [th:action="@{/logout}"] genererà l’attributo [action] del tag [form]. Il valore [@{/logout}] genererà il percorso [<context>/logout], dove [context] è il contesto dell'applicazione web;
Il codice HTML generato è il seguente:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1>Hello user!</h1>
<form method="post" action="/logout">
<input type="submit" value="Sign Out" />
<input type="hidden" name="_csrf" value="b152e5b9-d1a4-4492-b89d-b733fe521c91" />
</form>
</body>
</html>
- riga 8: la traduzione di Hello [[${#httpServletRequest.remoteUser}]]!;
- riga 9: la traduzione di @{/logout};
- riga 11: un campo nascosto denominato (attributo name) _csrf;
L'ultima vista [login.html] è la seguente:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">Invalid username and password.</div>
<div th:if="${param.logout}">You have been logged out.</div>
<form th:action="@{/login}" method="post">
<div>
<label> User Name : <input type="text" name="username" />
</label>
</div>
<div>
<label> Password: <input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Sign In" />
</div>
</form>
</body>
</html>
- riga 9: l'attributo [th:if="${param.error}"] fa sì che il tag <div> venga generato solo se l'URL che visualizza la pagina di login contiene il parametro [error] (http://context/login?error);
- riga 10: l'attributo [th:if="${param.logout}"] fa sì che il tag <div> venga generato solo se l'URL, che visualizza la pagina di login, contiene il parametro [logout] (http://context/login?logout);
- righe 11-23: un modulo HTML;
- riga 11: il modulo verrà inviato a URL [<context>/login], dove <context> è il contesto dell'applicazione web;
- riga 13: un campo di immissione denominato [username];
- riga 17: un campo di immissione denominato [password];
Il codice HTML generato è il seguente:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div>
You have been logged out.
</div>
<form method="post" action="/login">
<div>
<label>
User Name :
<input type="text" name="username" />
</label>
</div>
<div>
<label>
Password:
<input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Sign In" />
</div>
<input type="hidden" name="_csrf" value="ef809b0a-88b4-4db9-bc53-342216b77632" />
</form>
</body>
</html>
Si noti che alla riga 28 Thymeleaf ha aggiunto un campo nascosto denominato [_csrf].
19.2.3. Configurazione Spring MVC
![]() |
La classe [MvcConfig] configura il framework Spring MVC:
package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
- riga 7: l'annotazione [@Configuration] rende la classe [MvcConfig] una classe di configurazione;
- riga 8: la classe [MvcConfig] estende la classe [WebMvcConfigurerAdapter] per ridefinirne alcuni metodi;
- riga 10: ridefinizione di un metodo della classe padre;
- righe 11-16: il metodo [addViewControllers] consente di associare URL a viste HTML. Vengono effettuate le seguenti associazioni:
vista | |
/templates/home.html | |
/templates/hello.html | |
/templates/login.html |
Il suffisso [html] e la cartella [templates] sono i valori predefiniti utilizzati da Thymeleaf. Possono essere modificati tramite configurazione. La cartella [templates] deve trovarsi nella radice del Classpath del progetto:
![]() |
Sopra [1], le cartelle [java] e [resources] sono entrambe cartelle sorgente (source folders). Ciò implica che il loro contenuto si troverà nella radice del Classpath del progetto. Pertanto, in [2], le cartelle [hello] e [templates] si troveranno alla radice del Classpath.
19.2.4. Configurazione di Spring Security
![]() |
La classe [WebSecurityConfig] configura il framework Spring Security:
package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated();
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
- riga 9: l'annotazione [@Configuration] rende la classe [WebSecurityConfig] una classe di configurazione;
- riga 10: l'annotazione [@EnableWebSecurity] trasforma la classe [WebSecurityConfig] in una classe di configurazione di Spring Security;
- riga 11: la classe [WebSecurity] estende la classe [WebSecurityConfigurerAdapter] per ridefinirne alcuni metodi;
- riga 12: ridefinizione di un metodo della classe padre;
- righe 13-16: il metodo [configure(HttpSecurity http)] viene ridefinito per definire i diritti di accesso alle diverse classi URL dell’applicazione;
- riga 14: il metodo [http.authorizeRequests()] consente di associare i URL ai diritti di accesso. Vengono effettuate le seguenti associazioni:
regola | codice | |
accesso senza autenticazione | | |
accesso solo con autenticazione |
- riga 15: definisce il metodo di autenticazione. L'autenticazione avviene tramite un modulo accessibile a tutti. La disconnessione (logout) è anch'essa accessibile a tutti;
- righe 19-21: ridefiniscono il metodo [configure(AuthenticationManagerBuilder auth)] che gestisce gli utenti;
- riga 20: l’autenticazione avviene tramite utenti predefiniti in modo “fisso” [auth.inMemoryAuthentication()]. Un utente è qui definito con il login [user], la password [password] e il ruolo [USER]. È possibile assegnare gli stessi diritti agli utenti che hanno lo stesso ruolo;
19.2.5. Classe eseguibile
![]() |
La classe [Application] è la seguente:
package hello;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {
public static void main(String[] args) throws Throwable {
SpringApplication.run(Application.class, args);
}
}
- riga 8: l'annotazione [@EnableAutoConfiguration] richiede a Spring Boot (riga 3) di effettuare la configurazione che lo sviluppatore non avrà effettuato esplicitamente;
- riga 9: rende la classe [Application] una classe di configurazione Spring;
- riga 10: richiede la scansione della cartella della classe [Application] per individuare i componenti Spring. Le due classi [MvcConfig] e [WebSecurityConfig] verranno così individuate poiché presentano l'annotazione [@Configuration];
- riga 13: il metodo [main] della classe eseguibile;
- riga 14: il metodo statico [SpringApplication.run] viene eseguito con come parametro la classe di configurazione [Application]. Abbiamo già incontrato questo processo e sappiamo che il server Tomcat incluso nelle dipendenze Maven del progetto verrà avviato e il progetto verrà distribuito su di esso. Abbiamo visto che quattro URL erano gestiti da [/, /home, /login, /hello] e che alcuni erano protetti da diritti di accesso.
19.2.6. Test dell’applicazione
Iniziamo richiedendo il URL [/], che è uno dei quattro URL accettati. È associato alla vista [/templates/home.html]:
![]() |
La vista URL richiesta, [/], è accessibile a tutti. Ecco perché l’abbiamo ottenuta. Il link [here] è il seguente:
Il URL [/hello] verrà richiesto quando si cliccherà sul link. Questo è protetto:
regola | codice | |
accesso senza autenticazione | | |
accesso solo con autenticazione |
È necessario effettuare l'autenticazione per ottenerlo. Spring Security reindirizzerà quindi il browser del cliente alla pagina di autenticazione. In base alla configurazione visualizzata, si tratta della pagina URL [/login]. Questa è accessibile a tutti:
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
Otteniamo quindi [1]:
![]() |
Il codice sorgente della pagina ottenuta è il seguente:
- alla riga 7 compare un campo nascosto che non è presente nella pagina originale [login.html]. È stato aggiunto da Thymeleaf. Questo codice, denominato CSRF (Cross Site Request Forgery), ha lo scopo di eliminare una vulnerabilità di sicurezza. Questo token deve essere rinviato a Spring Security insieme all’autenticazione affinché quest’ultima venga accettata;
Ricordiamo che Spring Security riconosce solo l’utente user/password. Se inseriamo qualcos’altro in [2], otteniamo la stessa pagina con un messaggio di errore in [3]. Spring Security ha reindirizzato il browser verso URL [http://localhost:8080/login?error]. La presenza del parametro [error] ha attivato la visualizzazione del tag:
<div th:if="${param.error}">Invalid username and password.</div>
Ora inseriamo i valori previsti per user/password [4]:
![]() |
- in [4], effettuiamo l’autenticazione;
- in [5], Spring Security ci reindirizza verso URL [/hello] poiché era proprio URL che avevamo richiesto quando siamo stati reindirizzati alla pagina di login. L’identità dell’utente è stata visualizzata dalla seguente riga di [hello.html]:
La pagina [5] mostra il seguente modulo:
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
Quando si fa clic sul pulsante [Sign Out], verrà eseguito un POST su URL [/logout]. Quest’ultimo, così come il URL e il [/login], è accessibile a tutti:
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
Nella nostra associazione URL / viste, non abbiamo definito nulla per URL e [/logout]. Cosa succederà? Proviamo:
![]() |
- in [6], clicchiamo sul pulsante [Sign Out];
- In [7], vediamo che siamo stati reindirizzati verso URL e [http://localhost:8080/login?logout]. È stato Spring Security a richiedere questo reindirizzamento. La presenza del parametro [logout] in URL ha fatto visualizzare la seguente riga nella vista:
<div th:if="${param.logout}">You have been logged out.</div>
19.2.7. Conclusione
Nell’esempio precedente, avremmo potuto scrivere prima l’applicazione web e poi renderla sicura. Spring Security non è invasivo. È possibile implementare la sicurezza di un’applicazione web già scritta. Inoltre, abbiamo scoperto quanto segue:
- è possibile definire una pagina di autenticazione;
- l’autenticazione deve essere accompagnata dal token CSRF emesso da Spring Security;
- se l’autenticazione fallisce, si viene reindirizzati alla pagina di autenticazione con l’aggiunta di un parametro «error» nel token URL;
- se l’autenticazione va a buon fine, si viene reindirizzati alla pagina richiesta al momento dell’autenticazione. Se si richiede direttamente la pagina di autenticazione senza passare da una pagina intermedia, Spring Security reindirizza verso URL [/] (questo caso non è stato presentato);
- ci si disconnette richiedendo la pagina URL [/logout] con un POST. Spring Security ci reindirizza quindi alla pagina di autenticazione con il parametro logout nel URL;
Tutte queste conclusioni si basano sui comportamenti predefiniti di Spring Security. Tali comportamenti possono essere modificati tramite configurazione, ridefinendo alcuni metodi della classe [WebSecurityConfigurerAdapter].
Il tutorial precedente ci sarà di scarso aiuto nel prosieguo. Utilizzeremo infatti:
- un database per memorizzare gli utenti, le loro password e i loro ruoli;
- un'autenticazione tramite header HTTP;
Esistono pochi tutorial relativi a ciò che vogliamo realizzare in questa sede. La soluzione che verrà proposta è un insieme di codici trovati qua e là.














