Form authentication con Spring Security

In questo nuovo appuntamento con Spring Boot, parliamo di autenticazione.

Supponiamo di voler rendere “privato” l’accesso ai contatti della nostra fedele SuperRubricaTelefonica!, implementando un classico form di login.

Come fare?

Con Spring Boot e Spring Security bastano poche righe di codice.

Mettiamoci subito all’opera! :-)

Aggiungiamo Spring Security

Per cominciare, dobbiamo aggiungere una nuova dipendenza nel pom.xml del nostro progetto:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Il pacchetto spring-boot-starter-security implementa l’integrazione tra Spring Boot e Spring Security, fornendo utili configurazioni di default per questo framework.

Nota Per chi non lo conoscesse, Spring Security è il framework della famiglia Spring dedicato all’implementazione delle logiche di autenticazione e autorizzazione in progetti basati su Spring e Spring MVC.

Ora, se provate a lanciare l’applicazione dopo aver aggiunto solamente la nuova dipendenza, noterete che l’accesso a una qualsiasi delle URL della nostra rubrica richiede l’inserimento di username e password.

Spring Security default

Non spaventatevi… Il pacchetto spring-boot-starter-security introduce, di default, la basic authentication per tutti i percorsi previsti dalla nostra applicazione. Se ve lo state chiedendo, lo username da usare è: user, mentre la password autogenerata la potete leggere nei log di avvio dell’applicazione. Troverete qualcosa del genere:

Using default security password: be56656c-6070-4985-838a-9a41930ffa6f

Ovviamente, noi vogliamo creare qualcosa di più “carino” e accessibile per l’utente, quindi abbiamo bisogno di configurare Spring Security in base alle nostre esigenze.

Configurazione

Per effettuare l’override delle configurazioni predefinite e personalizzare il comportamento di Spring Security, abbiamo bisogno di una classe di configurazione.

Di seguito riporto un estratto di SecurityConfiguration, classe che useremo per definire le regole di autorizzazione:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
      .antMatchers("/css/**").permitAll()
      .anyRequest().authenticated()
      .and()
      .formLogin()
      .loginPage("/login").permitAll()
      .and()
      .logout().permitAll();
  }
}

Alcune osservazioni sul codice appena riportato:

  • l’annotazione @Configuration permette di definire configurazioni generali all’interno di un progetto Spring;

  • l’annotazione @EnableWebSecurity, definita da Spring Security, abilita l’integrazione con Spring MVC e permette di personalizzare le logiche di autenticazione effettuando l’override dei metodi dichiarati da WebSecurityConfigurerAdapter;

  • le nostre regole di autenticazione le definiamo all’interno del metodo configure.

Spring Security permette di utilizzare una sintassi “fluida”, che rende molto intuitivo e comprensibile quanto descritto dal codice.

In particolare, nel nostro caso stiamo impostando come “pubbliche” tutte le risorse presenti nella directory /css e nelle sue sub-directory, oltre alla pagina di login. Per l’accesso a tutte le altre URL, invece, è richiesta l’autenticazione.

Ma dove sono le credenziali e come vengono recuperate?

Solitamente le credenziali degli utenti registrati vengono mantenute nel database dell’applicazione. In questo tutorial non esamineremo la fase di registrazione, per cui creeremo una semplice tabella users nel nostro DB con alcuni account di test.

Piccola nota Poiché la SuperRubricaTelefonica! utilizza H2 come database engine, lo schema e i dati presenti nel DB sono inizializzati ad ogni avvio dell’applicazione. In ogni caso, qualunque motore di database stiate utilizzando, i file schema.sql e data.sql, presenti in src/main/resources, vi permettono di definire quali tabelle generare e quali dati utilizzare per inizializzare il vostro DB.

Implementiamo lo UserDetailsService

A questo punto dobbiamo “spiegare” a Spring Security dove andare a prendere gli utenti registrati e come verificare le loro credenziali.

Lo facciamo implementando l’interfaccia UserDetailsService:

@Service
public class SuperRubricaUserDetailsService implements UserDetailsService {

  @Autowired
  private UserRepository userRepository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = userRepository.findByUsername(username);

    if (user == null) {
      throw new UsernameNotFoundException("Username not found");
    }

    return new org.springframework.security.core.userdetails.User(
      username
      , user.getPassword()
      , Collections.singleton(new SimpleGrantedAuthority("user")));
  }
}

Come vedete, definiamo un Service che recupera le informazione dell’utente dal database (per il codice del Repository vi rimando a GitHub). La dependency injection di Spring fa il resto del lavoro per noi…

Poiché la classe è annotata con @Service, Spring provvederà a crearne un’istanza nell’application context. Spring Security rileverà la presenza di un’istanza “concreta” di UserDetailsService nel contesto dell’applicazione e utilizzerà il nuovo servizio per recuperare i dati necessari all’autenticazione.

Facile no?

Form di login

Passiamo ora alla creazione della pagina di login.

Il codice è molto semplice e prevede un classico form HTML:

<div>
  <h2>Accedi</h2>
  <div th:if="${param.error}">
    Nome utente o password errati!
  </div>
  <div th:if="${param.logout}">
    Hai effettuato il logout. A presto!
  </div>
  <form th:action="@{/login}" method="post">
    <div>
      <label>Username : <input type="text" name="username"/></label>
    </div>
    <div>
      <label>Password: <input type="password" name="password"/></label>
    </div>
    <div><input type="submit" value="Entra"/></div>
  </form>
</div>

Ho riportato la porzione di codice che c’interessa esaminare. In particolare:

  • la URL indicata in th:action deve coincidere con quella configurata precedentemente in SecurityConfiguration; la POST verrà gestita da Spring Security, per cui non sarà necessario definire un ulteriore Controller;

  • i nomi username e password utilizzati per i campi di input sono quelli previsti di default da Spring Security; qualunque altro nome verrà ignorato, a meno che non lo configuriate differentemente in SecurityConfiguration;

  • ho inserito due blocchi “alert”, visualizzati rispettivamente nel caso di credenziali errate e di logout; l’espressione ${param.<name>} permette di recuperare parametri passati in query string; error e logout sono parametri predefiniti di Spring Security.

Se lanciamo l’applicazione e proviamo a visualizzare la home page della nostra rubrica, dovremmo essere accolti da una fantastica pagina di login:

SuperRubrica login

… e c’è anche il logout

Ovviamente, è importante dare anche la possibilità di uscire dall’applicazione effettuando il logout.

Spring Security prevede già una URL di default, /logout , abilitata a ricevere richieste di tipo POST.

Potremmo quindi inserire nelle nostre pagine qualcosa del genere:

<form th:action="@{/logout}" method="post">
  <span sec:authentication="name"></span>
  <input type="submit" value="Esci"/>
</form>

Avrete notato lo span che ho incluso all’interno del form. L’attributo sec:authentication="name", di Thymeleaf, permette di ottenere lo username dell’utente attualmente autenticato.

Per poter utilizzare questo attributo è però necessario importare nel progetto il modulo thymeleaf-extras-springsecurity4, che integra alcune utili funzionalità di Spring Security in Thymeleaf:

<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>

Inoltre dovremo specificare nel tag html della pagina il namespace xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4".

In conclusione

Abbiamo visto come implementare la form authentication nella nostra applicazione web, sfruttando l’accoppiata Spring Boot + Spring Security.

Per il codice completo del tutorial vi rimando al repository GitHub (trovate il link in fondo all’articolo).

Se questo post vi è stato utile, cliccate sul social che vi sta più simpatico e condividete! ;-)

Alla prossima,

David