20. Protezione del servizio web di accesso al database [dbproduitscategories]
20.1. Configurazione dell’ambiente di lavoro
Implementeremo la sicurezza del servizio web con i seguenti progetti:
![]() |
- i progetti [spring-security-*] si trovano nella cartella [<exemples>\spring-database-generic\spring-security];
- la sicurezza verrà implementata per i progetti SGBD e MySQL con un livello [DAO / JDBC] seguito da un livello [DAO / JPA / Hibernate];
- premere Alt-F5 e rigenerare tutti i progetti Maven;
È necessario creare degli utenti nel database [dbproduitscategories]. A tal fine, utilizzare la configurazione di esecuzione [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
L'esecuzione di questa configurazione popola le tabelle [USERS, ROLES, USERS_ROLES] dalla tabella [dbproduitscategories]:
![]() |
![]() |
Gli identificativi creati [login/passwd] sono i seguenti: [admin/admin], [user/user], [guest/guest]. Nel database, le password sono crittografate.
![]() |

Fatto ciò, eseguite la configurazione di esecuzione denominata [spring-security-server-jpa-generic-hibernate-eclipselink] che avvia il servizio web protetto (MySQL deve essere già in esecuzione):
![]() | ![]() |
Quindi eseguire la configurazione di esecuzione denominata [spring-security-client-generic-JUnitTestDao] che verifica il servizio web protetto:
![]() | ![]() |
Il test dovrebbe avere esito positivo.
20.2. Il progetto Eclipse [spring-security-server-jdbc-generic]
Il servizio web protetto è implementato dal progetto [spring-security-server-jdbc-generic]:
![]() |
Sopra:
- il livello [DAO1] è il livello [DAO] che gestisce le tabelle [PRODUITS] e [CATEGORIES] del database [dbproduitscategories]. È già stato scritto;
- il livello [DAO2] è il livello [DAO] che gestisce le tabelle [USERS], [ROLES] e [USERS_ROLES] del database [dbproduitscategories]. Deve ancora essere scritto;
![]() |
20.2.1. La configurazione Maven
Il progetto [spring-security-server-jdbc-generic] è un progetto Maven configurato dal seguente file [pom.xml]:
<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>dvp.spring.database</groupId>
<artifactId>spring-security-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-server-jdbc-generic</name>
<description>démo spring security</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- server web / jSON -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- plugin -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- righe 29-33: si riprende quanto già esistente con l'archivio del servizio web / json / jdbc analizzato;
- righe 24-27: la dipendenza che introduce le classi di Spring Security;
Alla fine, il progetto presenta le seguenti dipendenze rispetto agli altri progetti caricati in Eclipse:
![]() |
20.2.2. Il livello [DAO2]
![]() |
Sopra:
- il livello [DAO1] è il livello [DAO] che gestisce le tabelle [PRODUITS] e [CATEGORIES] del database [dbproduitscategories]. È già stato scritto;
- il livello [DAO2] è il livello [DAO] che gestisce le tabelle [USERS], [ROLES] e [USERS_ROLES] del database [dbproduitscategories]. È quella che scriveremo ora;
![]() |
Spring Security richiede la creazione di una classe che implementi la seguente interfaccia [UsersDetail]:
![]() |
Questa interfaccia è implementata qui dalla classe [AppUserDetails]:
package spring.security.dao;
import generic.jdbc.config.ConfigJdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import spring.jdbc.entities.Role;
import spring.jdbc.entities.User;
import spring.jdbc.infrastructure.DaoException;
public class AppUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
// JdbcTemplate
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// proprietà
private User user;
private String simpleClassName = getClass().getSimpleName();
// costruttori
public AppUserDetails() {
}
public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.user = user;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
// -------------------------interfaccia
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : getRoles(user.getId())) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
...
// metodi privati----------------
private List<Role> getRoles(Long id) {
try {
// ricerca l'utente tramite il suo ID
return namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_ROLES_BYUSERID, Collections.singletonMap("id", id), new ShortRoleMapper());
} catch (Exception e) {
//e.printStackTrace();
throw new DaoException(167, e, simpleClassName);
}
}
}
// --------------------- mappatori
class ShortRoleMapper implements RowMapper<Role> {
@Override
public Role mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Role(rs.getLong("r_ID"), rs.getLong("r_VERSIONING"), rs.getString("r_NAME"));
}
}
- riga 22: la classe [AppUserDetails] implementa l'interfaccia [UserDetails];
- righe 29-30: la classe incapsula un utente (riga 19) e il repository che consente di ottenere i dettagli di tale utente (riga 20);
- riga 27: l'accesso al database avverrà tramite JDBC utilizzando l'oggetto [NamedParameterJdbcTemplate namedParameterJdbcTemplate] definito nel progetto [spring-jdbc-generic-04]. Si noti che questo oggetto non viene iniettato da Spring come spesso avveniva in passato. Viene fornito al costruttore nelle righe 36-39. Perché? Perché la classe [AppUserDetails] non è un componente Spring (manca l’annotazione @Component) e quindi non è possibile effettuare iniezioni al suo interno;
- righe 36-39: il costruttore che istanzia la classe con un utente e il relativo repository;
- righe 42-49: implementazione del metodo [getAuthorities] dell’interfaccia [UserDetails]. Deve costruire una collezione di elementi di tipo [GrantedAuthority] o derivati. Qui utilizziamo il tipo derivato [SimpleGrantedAuthority] (riga 46) che incapsula il nome di uno dei ruoli dell’utente della riga 29;
- righe 45-47: si percorre l’elenco dei ruoli dell’utente della riga 29 per costruire un elenco di elementi di tipo [SimpleGrantedAuthority];
- riga 45: per ottenere i ruoli dell’utente, si utilizza il metodo privato [getRoles] della riga 53;
- riga 56: esegue il comando SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] (definito in [Configjdbc]) come segue:
public static final String SELECT_ROLES_BYUSERID = "SELECT DISTINCT r.ID as r_ID, r.VERSIONING as r_VERSIONING, r.NAME as r_NAME FROM ROLES r, USERS u, USERS_ROLES ur WHERE u.ID=:id AND ur.USER_ID=u.ID AND ur.ROLE_ID=r.ID";
Questa query SQL esegue un join tra le tre tabelle [USERS, ROLES, USERS_ROLES] per ottenere i ruoli di un utente identificato dalla sua chiave primaria. È configurata con la chiave primaria [:id] dell’utente di cui si cercano i ruoli.
- riga 56: ogni riga risultante da [SELECT] viene trasformata in un'entità [Role] dalla classe [ShortRowMapper] delle righe 66-72;
Torniamo al codice della classe [AppUserDetails]:
package spring.security.dao;
...
public class AppUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
// JdbcTemplate
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// proprietà
private User user;
private String simpleClassName = getClass().getSimpleName();
// costruttori
public AppUserDetails() {
}
public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.user = user;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
// -------------------------interfaccia
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : getRoles(user.getId())) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getLogin();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// getter e setter
...
}
- righe 35-37: implementano il metodo [getPassword] dell’interfaccia [UserDetails]. Viene restituita la password dell’utente della riga 12;
- righe 39-42: implementano il metodo [getUserName] dell'interfaccia [UserDetails]. Viene restituito il nome utente della riga 12;
- righe 44-47: l'account dell'utente non scade mai;
- righe 49-52: l'account dell'utente non viene mai bloccato;
- righe 54-57: le credenziali dell'utente non scadono mai;
- righe 59-62: l’account dell’utente è sempre attivo;
Spring Security richiede inoltre l’esistenza di una classe che implementi l’interfaccia [AppUserDetailsService]:
![]() |
Questa interfaccia è implementata dalla seguente classe [AppUserDetailsService]:
package spring.security.dao;
import generic.jdbc.config.ConfigJdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import spring.jdbc.entities.User;
import spring.jdbc.infrastructure.DaoException;
@Service
public class AppUserDetailsService implements UserDetailsService {
// iniezioni
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// locale
private String simpleClassName = getClass().getSimpleName();
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
List<User> users;
try {
// si cerca l'utente tramite il suo login
users = namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_USER_BYLOGIN,
Collections.singletonMap("login", login), new ShortUserMapper());
} catch (Exception e) {
throw new DaoException(145, e, simpleClassName);
}
// Trovato?
if (users.size() == 0) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// vengono restituiti i dettagli dell'utente
return new AppUserDetails(users.get(0), namedParameterJdbcTemplate);
}
}
// --------------------- mappers
class ShortUserMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getLong("u_ID"), rs.getLong("u_VERSIONING"), rs.getString("u_NAME"), rs.getString("u_LOGIN"),
rs.getString("u_PASSWORD"));
}
}
- riga 21: la classe sarà un componente Spring;
- righe 25-26: l'accesso al database avverrà tramite JDBC utilizzando l'oggetto [NamedParameterJdbcTemplate namedParameterJdbcTemplate] definito nei bean del progetto [spring-jdbc-generic-04];
- righe 31-49: implementazione del metodo [loadUserByUsername] dell’interfaccia [UserDetailsService] (riga 22). Il parametro è il login dell’utente;
- righe 36-37: l'utente viene cercato tramite il suo login. L'ordine SQL [ConfigJdbc.SELECT_USER_BYLOGIN] è il seguente:
public static final String SELECT_USER_BYLOGIN = "SELECT u.ID as u_ID, u.VERSIONING as u_VERSIONING, u.NAME as u_NAME,u.LOGIN as u_LOGIN,u.PASSWORD as u_PASSWORD FROM USERS u WHERE u.LOGIN= :login";
Ogni riga restituita da SELECT viene trasformata in entità [User] dalla classe [ShortUserMapper] delle righe 52-58.
- righe 42-44: se non viene trovato, viene generata un'eccezione;
- riga 46: viene creato e restituito un oggetto [AppUserDetails]. È effettivamente di tipo [UserDetails] (riga 32). Al suo costruttore vengono passate due informazioni:
- l'utente che è stato trovato;
- l'oggetto [namedParameterJdbcTemplate] che consentirà alla classe [AppUserDetails] di interrogare il database;
20.2.3. Il livello [web]
![]() |
Il progetto [spring-security-server-jdbc-generic] dipende dal progetto [spring-webjson-server-jdbc-generic]:
![]() |
È questo progetto che implementa il livello [web]. Non è necessario modificarlo.
20.2.4. Configurazione della sicurezza del progetto
![]() |
Il progetto è configurato dalla seguente classe [AppConfig]:
1 ![]() |
Abbiamo già incontrato una classe di configurazione di Spring Security (cfr. paragrafo 19.2.4):
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");
}
}
Seguiremo la stessa procedura:
- riga 11: definire una classe che estenda la classe [WebSecurityConfigurerAdapter];
- riga 13: definire un metodo [configure(HttpSecurity http)] che definisca i diritti di accesso alle diverse URL del servizio web;
- riga 19: definire un metodo [configure(AuthenticationManagerBuilder auth)] che definisce gli utenti e i loro ruoli;
La classe [AppConfig] sarà la seguente:
package spring.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import spring.security.dao.AppUserDetailsService;
import spring.webjson.server.config.WebConfig;
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = { "spring.security.dao", "spring.security.service" })
@Import({ spring.webjson.server.config.AppConfig.class, WebConfig.class })
public class AppConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AppUserDetailsService appUserDetailsService;
// protezione
private boolean activateSecurity = true;
@Override
protected void configure(AuthenticationManagerBuilder registry) throws Exception {
// l'autenticazione viene effettuata dal bean [appUserDetailsService]
// la password è crittografata tramite l'algoritmo di hash BCrypt
registry.userDetailsService(appUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// applicazione sicura?
if (activateSecurity) {
// la password viene trasmessa tramite l'intestazione Authorization: Basic xxxx
http.httpBasic();
// il metodo HTTP OPTIONS deve essere autorizzato per tutti
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// solo il ruolo ADMIN può utilizzare l’applicazione
http.authorizeRequests() //
.antMatchers("/", "/**") // tutte le sessioni URL
.hasRole("ADMIN");
// con o senza sessione?
//http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
}
- riga 17: la classe è una classe di configurazione Spring;
- riga 18: attiva gli elementi di Spring Security;
- riga 26: si recuperano i componenti Spring del livello [DAO2] e quelli del pacchetto [spring.security.service] di cui parleremo in seguito;
- riga 23: si importano i bean del progetto [spring-webjson-server-jdbc-generic] che implementa il livello [web]. Tra questi bean vi sono anche quelli del livello [DAO1];
- righe 22-23: viene iniettata la classe [AppUserDetails] che consente l'accesso agli utenti dell'applicazione;
- riga 26: un valore booleano che protegge (true) o meno (false) l'applicazione web;
- righe 28-33: il metodo [configure(HttpSecurity http)] definisce gli utenti e i loro ruoli. Accetta come parametro un tipo [AuthenticationManagerBuilder]. Questo parametro viene arricchito con due informazioni (riga 32):
- un riferimento al servizio [appUserDetailsService] della riga 23 che consente l'accesso agli utenti registrati. Si noti che il fatto che siano registrati in un database non risulta esplicito. Potrebbero quindi trovarsi in una cache, essere forniti da un servizio web, ...
- il tipo di crittografia utilizzato per la password. Abbiamo utilizzato l’algoritmo BCrypt;
- righe 35-53: il metodo [configure(HttpSecurity http)] definisce i diritti di accesso ai URL del servizio web;
- riga 38: abbiamo visto nel progetto introduttivo che, per impostazione predefinita, Spring Security gestiva un token CSRF (Cross Site Request Forgery) che l’utente che desiderava autenticarsi doveva rinviare al server. Qui questo meccanismo è disattivato. Questo, insieme al valore booleano (isSecured=false), consente di utilizzare l’applicazione web senza sicurezza;
- riga 42: si attiva la modalità di autenticazione tramite l'intestazione HTTP. Il client dovrà inviare la seguente intestazione HTTP:
dove «code» è la codifica della stringa «login:password» tramite l’algoritmo Base64. Ad esempio, la codifica Base64 della stringa admin:admin è YWRtaW46YWRtaW4=. Pertanto, l'utente con nome utente [admin] e password [admin] invierà la seguente intestazione HTTP per autenticarsi:
- righe 47-49: indicano che tutti i URL del servizio web sono accessibili agli utenti con il ruolo [ROLE_ADMIN]. Ciò significa che un utente che non possiede questo ruolo non può accedere al servizio web;
- riga 51: in modalità [session], un utente che si è autenticato una volta non dovrà farlo per gli accessi successivi. Questo è il valore predefinito di Spring Security. La riga 51 disattiva questa modalità. Se è attiva, l’utente dovrà autenticarsi ad ogni accesso. Senza sessione, la reattività del servizio web protetto è inferiore rispetto a quella con la sessione, pertanto la riga 51 è stata commentata;
20.2.5. Test del servizio web protetto
Testeremo il servizio web con il client Chrome [Advanced Rest Client]. Dovremo specificare l'intestazione di autenticazione HTTP:
dove [code] è il codice Base64 della stringa [login:password]. Per generare questo codice, è possibile utilizzare il seguente programma del progetto [spring-security-create-users]:
![]() |
package spring.security.helpers;
import org.springframework.security.crypto.codec.Base64;
public class Base64Encoder {
public static void main(String[] args) {
// si attendono due argomenti: login e password
if (args.length != 2) {
System.out.println("Syntaxe : login password");
System.exit(0);
}
// si recuperano i due argomenti
String chaîne = String.format("%s:%s", args[0], args[1]);
// si codifica la stringa
byte[] data = Base64.encode(chaîne.getBytes());
// si visualizza la sua codifica Base64
System.out.println(new String(data));
}
}
Se eseguiamo questo programma con i due argomenti [admin admin]:
![]() |
otteniamo il seguente risultato:
Ora siamo pronti per i test:
- è necessario avviare SGBD e MySQL;
- compiliamo le tabelle [PRODUITS] e [CATEGORIES] con la configurazione di esecuzione denominata [spring-jdbc-generic-04-fillDataBase]:
![]() |
- se non è già stato fatto, popoliamo le tabelle [USERS, ROLES, USERS_ROLES] con la configurazione di esecuzione denominata [spring-security-create-users-hibernate-eclipselink]:
![]() |
- avviamo il servizio web protetto con la configurazione di esecuzione denominata [spring-security-server-jdbc-generic]:
![]() |
Quindi, con il client Chrome [Advanced Rest Client], richiediamo la versione estesa di tutte le categorie:
![]() |
- in [1], richiediamo l’URL delle categorie estese;
- in [2], con un metodo GET;
- in [3], forniamo l'intestazione HTTP dell'autenticazione. Il codice [YWRtaW46YWRtaW4=] è la codifica Base64 della stringa [admin:admin];
- in [4], inviamo il comando HTTP;
La risposta del server è la seguente:
![]() |
- in [1], l'intestazione di autenticazione HTTP;
- in [2], il server restituisce una risposta jSON;
Si ottiene effettivamente l'elenco delle categorie:
![]() |
Proviamo ora a inviare una richiesta HTTP con un'intestazione di autenticazione errata. La risposta è quindi la seguente:
![]() |
- in [1]: l'intestazione di autenticazione HTTP;
Otteniamo la seguente risposta:
![]() |
- in [2]: la risposta del servizio web;
Ora proviamo con l'utente user / user. Esiste ma non ha accesso al servizio web. Se eseguiamo il programma di codifica Base64 con i due argomenti [user user]:
![]() |
otteniamo il seguente risultato:
![]() |
- in [1]: l’intestazione di autenticazione errata HTTP;
![]() |
- in [2]: la risposta del servizio web. È diversa dalla precedente, che era [401 Unauthorized]. Questa volta, l'utente si è autenticato correttamente ma non dispone dei diritti sufficienti per accedere a URL;
Un servizio web protetto è ora operativo.
20.2.6. Un codice di autenticazione URL
![]() |
Creeremo un URL che ci consentirà di verificare se un utente è autorizzato o meno ad accedere al servizio web. A tal fine, creiamo il seguente nuovo controller MVC [AuthenticateController]:
package spring.security.service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import spring.webjson.server.service.Response;
@RestController
public class AuthenticateController {
@RequestMapping(value = "/authenticate", method = RequestMethod.GET)
public Response<Void> authenticate() {
return new Response<Void>(0, null, null);
}
}
- riga 9: la classe [AuthenticateController] è un controller Spring. In quanto tale, espone i URL. L'annotazione [@RestController] indica che i metodi che gestiscono questi URL restituiscono essi stessi la risposta al client;
- riga 11: restituisce URL [/authenticate];
- righe 12-14: il metodo si limita a restituire un oggetto [Response] vuoto ma con un [status] pari a 0, a indicare che non si è verificato alcun errore;
A cosa serve questo URL? Quando vorremo semplicemente autenticare un utente, lo richiederemo. Abbiamo visto che se il livello di sicurezza non accetta quell’utente, restituisce un’eccezione. Ecco un esempio;
Con l’utente [admin:admin]:
![]() | ![]() |
Si ottiene una risposta vuota ma nessuna eccezione.
Con l’utente [user:user]:
![]() | ![]() |
Si è verificata un'eccezione.
20.2.7. Conclusione
L'aggiunta delle classi necessarie a Spring Security è stata possibile senza modifiche al progetto web/json originale. Questo caso particolarmente favorevole deriva dal fatto che le tre tabelle aggiunte nel database sono indipendenti da quelle esistenti. Si sarebbe potuto persino inserirle in un database separato. In altri casi, le tabelle aggiunte possono avere relazioni con quelle esistenti. Il codice del livello [DAO] esistente deve quindi essere rivisto.
20.3. Un client programmato per il servizio web / jSON protetto
Abbiamo già scritto un client per il servizio web / jSON non protetto:
![]() |
Ora creeremo un client programmato per il servizio web protetto:
![]() |
![]() |
20.3.1. Il livello [Client HTTP]
![]() |
![]() | ![]() |
La classe [Client] garantisce la comunicazione HTTP con il server web / jSON protetto. Come abbiamo appena visto, in questa comunicazione HTTP, il client deve ora inviare un'intestazione di autenticazione, ad esempio:
L’interfaccia [IClient] diventa la seguente:
package spring.webjson.client.dao;
import org.springframework.http.HttpMethod;
import spring.webjson.client.entities.Credentials;
public interface IClient {
public <T1, T2> T1 getResponse(Credentials credentials,String url, HttpMethod method, int errStatus, T2 body);
}
- riga 8: il primo parametro del metodo [getResponse] è ora un oggetto [Credentials] che incapsula le credenziali di un utente:
package spring.security.client.entities;
public class Credentials {
// proprietà
private String login;
private String password;
// costruttore
public Credentials() {
}
public Credentials(String login, String password) {
this.login = login;
this.password = password;
}
// getter e setter
...
}
La classe [Client], che implementa l’interfaccia [IClient], viene modificata come segue:
package spring.security.client.dao;
...
@Component
public class Client implements IClient {
// iniezioni
@Autowired
protected RestTemplate restTemplate;
@Autowired
protected String urlServiceWebJson;
// locale
private String simpleClassName = getClass().getSimpleName();
private String getBase64(Credentials credentials) {
// si codificano in base 64 l'utente e la sua password - richiede Java 8
String chaîne = String.format("%s:%s", credentials.getLogin(), credentials.getPassword());
return String.format("Basic %s", new String(Base64.getEncoder().encode(chaîne.getBytes())));
}
// richiesta generica
@Override
public <T1, T2> T1 getResponse(Credentials credentials, String url, HttpMethod method, int errStatus, T2 body) {
// la risposta del server
ResponseEntity<Response<T1>> response;
try {
// si prepara la richiesta
RequestEntity<?> request = null;
if (method == HttpMethod.GET) {
HeadersBuilder<?> headersBuilder = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url))) .accept(MediaType.APPLICATION_JSON);
if (credentials != null) {
headersBuilder = headersBuilder.header("Authorization", getBase64(credentials));
}
request = headersBuilder.build();
}
if (method == HttpMethod.POST) {
BodyBuilder bodyBuilder = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url))).header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON);
if (credentials != null) {
bodyBuilder = bodyBuilder.header("Authorization", getBase64(credentials));
}
request = bodyBuilder.body(body);
}
// si esegue la richiesta
response = restTemplate.exchange(request, new ParameterizedTypeReference<Response<T1>>() {
});
} catch (Exception e) {
// si incapsula l'eccezione
throw new DaoException(errStatus, e, simpleClassName);
}
...
}
...
}
- righe 33-35, 40-42: se l'utente [credentials] non è nullo, viene aggiunta l'intestazione di autenticazione. La codifica Base64 dell’utente e della sua password è garantita dal metodo [getBase64] delle righe 17-21. Si noti che questo metodo utilizza una classe [Base64] appartenente alla versione 1.8 di JDK. Il nostro client HTTP può funzionare con un servizio web non protetto. È sufficiente passargli un [credentials] uguale a null;
- a parte le righe precedenti, il codice rimane invariato;
20.3.2. Il livello [DAO]
![]() |
20.3.2.1. L'interfaccia [IDao]
![]() |
Tutti i metodi dell'interfaccia [IDao] del progetto [spring-webjson-client-generic] ricevono un parametro aggiuntivo [Credentials credentials]:
package spring.security.client.dao;
import java.util.List;
import spring.security.client.entities.AbstractCoreEntity;
import spring.security.client.entities.Credentials;
public interface IDao<T extends AbstractCoreEntity> extends IAuthenticate {
// elenco di tutte le entità T
public List<T> getAllShortEntities(Credentials credentials);
public List<T> getAllLongEntities(Credentials credentials);
// di entità specifiche - versione breve
public List<T> getShortEntitiesById(Credentials credentials, Iterable<Long> ids);
public List<T> getShortEntitiesById(Credentials credentials, Long... ids);
public List<T> getShortEntitiesByName(Credentials credentials, Iterable<String> names);
public List<T> getShortEntitiesByName(Credentials credentials, String... names);
// di entità specifiche - versione estesa
public List<T> getLongEntitiesById(Credentials credentials, Iterable<Long> ids);
public List<T> getLongEntitiesById(Credentials credentials, Long... ids);
public List<T> getLongEntitiesByName(Credentials credentials, Iterable<String> names);
public List<T> getLongEntitiesByName(Credentials credentials, String... names);
// aggiornamento di più entità
public List<T> saveEntities(Credentials credentials, Iterable<T> entities);
public List<T> saveEntities(Credentials credentials, @SuppressWarnings("unchecked") T... entities);
// eliminazione di tutte le entità
public void deleteAllEntities(Credentials credentials);
// eliminazione di più entità
public void deleteEntitiesById(Credentials credentials, Iterable<Long> ids);
public void deleteEntitiesById(Credentials credentials, Long... ids);
public void deleteEntitiesByName(Credentials credentials, Iterable<String> names);
public void deleteEntitiesByName(Credentials credentials, String... names);
public void deleteEntitiesByEntity(Credentials credentials, Iterable<T> entities);
public void deleteEntitiesByEntity(Credentials credentials, @SuppressWarnings("unchecked") T... entities);
}
- riga 8: l'interfaccia [IDao] estende la seguente interfaccia [IAuthenticate]:
package spring.security.client.dao;
import spring.security.client.entities.Credentials;
public interface IAuthenticate {
// autenticazione
public void authenticate(Credentials credentials);
}
L'interfaccia [IAuthenticate] ha un unico metodo: [authenticate]. Questo metodo non restituisce alcun valore (void) se l'utente [Credentials credentials] viene accettato dal servizio web protetto; in caso contrario, genera un'eccezione.
20.3.2.2. La classe [AbstractDao]
![]() |
Si ricorda che la classe [AbstractDao] è la classe padre delle classi [DaoCategorie], che gestiscono i URL delle categorie, e [DaoProduit], che gestisce i URL dei prodotti. Tutti i metodi della classe [AbstractDao] del progetto [spring-webjson-client-generic] ricevono un parametro aggiuntivo [Credentials credentials] che passano alla classe figlia. Ecco un esempio:
@Override
public List<T1> getShortEntitiesById(Credentials credentials, Iterable<Long> ids) {
// validità dell'argomento
List<T1> entities = checkNullOrEmptyArgument(true, ids);
if (entities != null) {
return entities;
}
// risultato
return getShortEntitiesById(credentials, Lists.newArrayList(ids));
}
- il metodo [getShortEntitiesById] riceve il parametro [Credentials credentials] (riga 2) che trasmette (riga 9) al metodo [getShortEntitiesById] della classe figlia;
La classe [AbstractDao] ha la seguente struttura:
package spring.security.client.dao;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import spring.security.client.entities.AbstractCoreEntity;
import spring.security.client.entities.Credentials;
import spring.security.client.infrastructure.MyIllegalArgumentException;
import com.google.common.collect.Lists;
public abstract class AbstractDao<T1 extends AbstractCoreEntity> implements IDao<T1> {
@Autowired
private IAuthenticate authenticate;
...
}
- riga 14: la classe implementa l'interfaccia [IDao] che abbiamo descritto;
- righe 16-17: viene iniettata un'istanza dell'interfaccia [IAuthenticate]. Questa è implementata dalla seguente classe [Authenticate]:
package spring.security.client.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import spring.security.client.entities.Credentials;
@Component
public class Authenticate implements IAuthenticate{
@Autowired
protected IClient client;
// verifica [credentials,mdp]
public void authenticate(Credentials credentials) {
client.<Void, Void> getResponse(credentials, "/authenticate", HttpMethod.GET, 111, (Void) null);
}
}
- riga 9: la classe [Authenticate] è un componente Spring;
- riga 10: che implementa l'interfaccia [IAuthenticate];
- righe 11-12: iniezione del client HTTP che consente di interagire con il servizio web protetto;
- righe 15-17: implementazione del metodo [authenticate] dell’interfaccia;
- riga 16: viene emesso un comando HTTP GET verso URL [/authenticate]. L'utilizzo di questo URL è stato illustrato nel paragrafo 20.2.6. Il principio è che la chiamata termina con un'eccezione se l'utente [credentials] è sconosciuto o non dispone dei diritti sufficienti;
La classe [AbstractDao] implementa il metodo [authenticate] dell'interfaccia [IDao] nel modo seguente:
@Autowired
private IAuthenticate authenticate;
@Override
public void authenticate(Credentials credentials) {
authenticate.authenticate(credentials);
}
- riga 7: l'operazione viene delegata al metodo [authenticate] della classe [Authenticate]. Si verificherà quindi un'eccezione se l'utente [Credentials credentials] non è accettato dal servizio web protetto;
20.3.2.3. Le classi [DaoCategorie, DaoProduit]
![]() |
Le classi [DaoCategorie, DaoProduit] sono quelle del progetto [spring-webjson-server-generic] con il parametro aggiuntivo [Credentials credentials]. Ecco un esempio:
@Component
public class DaoCategorie extends AbstractDao<Categorie> {
// iniezioni
@Autowired
protected ApplicationContext context;
@Autowired
protected IClient client;
@Override
public List<Categorie> getAllShortEntities(Credentials credentials) {
try {
// filtri jSON
ObjectMapper mapper = context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// recupera tutte le categorie
Object map = client.<List<Categorie>, Void> getResponse(credentials, "/getAllShortCategories", HttpMethod.GET,
202, null);
// l'elenco delle categorie List<Categoria>
return mapper.readValue(mapper.writeValueAsString(map), new TypeReference<List<Categorie>>() {
});
} catch (DaoException e1) {
throw e1;
} catch (Exception e2) {
throw new DaoException(221, e2, simpleClassName);
}
}
....
20.3.3. La configurazione Spring
![]() |
La classe [AppConfig] configura l’ambiente Spring del progetto. È identica a quella presente nel progetto [spring-webjson-client-generic], tranne che per un dettaglio:
@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
- riga 2: occorre inserire il pacchetto del nuovo livello [DAO];
20.3.4. Test del livello [DAO]
![]() |
![]() |
20.3.4.1. Il test [JUnitTestCredentials]
Il test [JUnitTestCredentials] utilizza il metodo [IDao.authenticate] per verificare la validità o meno di determinati utenti:
package client.tests.junit;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import spring.security.client.config.AppConfig;
import spring.security.client.dao.IAuthenticate;
import spring.security.client.entities.Credentials;
import spring.security.client.infrastructure.DaoException;
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestCredentials {
// livello [DAO]
@Autowired
private IAuthenticate authenticate;
// utenti
static private Credentials admin;
static private Credentials user;
static private Credentials unknown;
@BeforeClass
public static void init() {
admin = new Credentials("admin", "admin");
user = new Credentials("user", "user");
unknown = new Credentials("x", "y");
}
@Test()
public void checkUserUser() {
DaoException se = null;
try {
authenticate.authenticate(user);
} catch (DaoException e) {
se = e;
System.out.println("checkUserUser: " + e);
}
Assert.assertNotNull(se);
Assert.assertEquals("403 Forbidden", se.getExceptions().get(0).getErrorMessage());
}
@Test()
public void checkUserUnknown() {
DaoException se = null;
try {
authenticate.authenticate(unknown);
} catch (DaoException e) {
se = e;
System.out.println("checkUserUnknown : " + e);
}
Assert.assertNotNull(se);
Assert.assertEquals("401 Unauthorized", se.getExceptions().get(0).getErrorMessage());
}
@Test()
public void checkUserAdmin() {
DaoException se = null;
try {
authenticate.authenticate(admin);
} catch (DaoException e) {
se = e;
System.out.println("checkUserAdmin : " + e);
}
Assert.assertNull(se);
}
}
- durante l'inizializzazione della classe di test, righe 29-34, vengono creati tre utenti:
- l'utente [admin] ha accesso ai URL del servizio web. Viene verificato alle righe 63-72;
- l'utente [user] esiste ma non è autorizzato a utilizzare i URL del servizio web. Viene testato alle righe 37-47;
- l'utente [unknown] non esiste. Viene verificato alle righe 50-60;
Si avvia il servizio web protetto con la configurazione di esecuzione denominata [spring-security-server-jdbc-generic] [1]:
![]() |
Si avvia quindi il test JUnit [JUnitTestCredentials] con la configurazione di esecuzione [spring-security-client-generic-JUnitTestCredentials] [2]. I risultati ottenuti nella console sono i seguenti:
e il test ha esito positivo:
![]() |
20.3.4.2. Il test [JUnitTestDao]
Il test [JUnitTestDao] è identico a quello del progetto non protetto [spring-webjson-client-generic], se nonse ora i metodi del livello [DAO] testati hanno tutti come primo parametro l'utente [admin / admin]:
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestDao {
// contesto Spring
@Autowired
private ApplicationContext context;
// livello [DAO]
@Autowired
private IDao<Produit> daoProduit;
@Autowired
private IDao<Categorie> daoCategorie;
....
// utenti
static private Credentials admin;
@BeforeClass
public static void init() {
admin = new Credentials("admin", "admin");
}
@Before
public void clean() {
// si pulisce il database prima di ogni test
log("Vidage de la base de données", 1);
// si svuota la tabella [CATEGORIES] e, di conseguenza, la tabella [PRODUITS]
daoCategorie.deleteAllEntities(admin);
// si svuotano i dizionari
for (Long id : mapCategories.keySet()) {
mapCategories.remove(id);
}
for (Long id : mapProduits.keySet()) {
mapProduits.remove(id);
}
}
private List<Categorie> fill(int nbCategories, int nbProduits) {
// si riempiono le tabelle
...
// si aggiunge la categoria - di conseguenza anche i prodotti verranno
// inseriti - si restituisce il risultato contemporaneamente
return daoCategorie.saveEntities(admin, categories);
}
private Object[] showDataBase() throws BeansException, JsonProcessingException {
// elenco delle categorie
log("Liste des catégories", 2);
List<Categorie> categories = daoCategorie.getAllShortEntities(admin);
affiche(categories, context.getBean("jsonMapperShortCategorie", ObjectMapper.class));
// elenco dei prodotti
log("Liste des produits", 2);
List<Produit> produits = daoProduit.getAllShortEntities(admin);
affiche(produits, context.getBean("jsonMapperShortProduit", ObjectMapper.class));
// risultato
return new Object[] { categories, produits };
}
...
Tutte le operazioni vengono eseguite con l’utente [admin / admin], l’unico ad avere il diritto di accesso al servizio web protetto.
Il test verrà avviato con la configurazione di esecuzione denominata [spring-security-client-generic-JUnitTestDao]:
![]() |
Il test viene superato, ma si nota che è più lento rispetto al servizio web non protetto. La protezione di un’applicazione ne aumenta sensibilmente i tempi di risposta. Si può notare un fattore importante nelle prestazioni del servizio web protetto: nella classe [AppConfig] che lo configura, abbiamo scritto:
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// applicazione sicura?
if (activateSecurity) {
// la password viene trasmessa tramite l'header Authorization: Basic xxxx
http.httpBasic();
// il metodo HTTP OPTIONS deve essere autorizzato per tutti
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// solo il ruolo ADMIN può utilizzare l’applicazione
http.authorizeRequests() //
.antMatchers("/", "/**") // tutte le URL
.hasRole("ADMIN");
// nessuna sessione
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
La riga 17 ha un costo. Essa determina se l’utente debba o meno autenticarsi ad ogni accesso. Se la si mette tra commenti, la durata del test JUnit è nettamente inferiore, questo perché l’utente [admin] si autentica solo per il primo test e non per quelli successivi (anche se l’intestazione di autenticazione HTTP viene inviata dal client, il server non verifica nuovamente la password dell’utente).
20.4. Il progetto Eclipse [spring-security-server-jpa-generic]
Il servizio web protetto verrà ora implementato dal progetto [spring-security-server-jpa-generic], che si basa sul progetto [spring-jpa-generic], il quale gestisce gli accessi al database con Spring Data JPA:
![]() |
Sopra:
- il livello [DAO1] è il livello [DAO] che gestisce le tabelle [PRODUITS] e [CATEGORIES] del database [dbproduitscategories]. È già stato scritto;
- il livello [DAO2] è il livello [DAO] che gestisce le tabelle [USERS], [ROLES] e [USERS_ROLES] del database [dbproduitscategories]. Deve ancora essere scritto;
Il progetto [spring-security-server-jpa-generic] si ottiene innanzitutto copiando il progetto esaminato in precedenza, [spring-security-server-jdbc-generic]. Infatti, i livelli [web] e [security] non cambiano perché:
- il livello [DAO1 / Repositories / JPA] (già scritto) ha la stessa interfaccia del livello [DAO1 / JDBC];
- il livello [DAO2 / Repositories / JPA] (da scrivere) avrà la stessa interfaccia del livello [DAO2 / JDBC];
Il progetto [spring-security-server-jpa-generic] è il seguente:
![]() |
- il pacchetto [spring.security.repositories] implementa il livello [repositories];
- il pacchetto [spring.security.dao] implementa il livello [dao2];
20.4.1. Il progetto Maven
Il progetto è un progetto Maven configurato dal seguente file [pom.xml]:
<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>dvp.spring.database</groupId>
<artifactId>spring-security-server-jpa-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-server-jpa-generic</name>
<description>démo spring security</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- server web / jSON -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jpa-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- plugin -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- righe 24-27: la dipendenza dal livello [security] del progetto;
- righe 29-33: la dipendenza per il livello [web] del progetto. Il progetto [spring-webjson-server-jpa-generic] implementa completamente il livello [web]. Quest'ultimo non deve essere scritto né modificato;
In definitiva, le dipendenze sono le seguenti:
![]() |
20.4.2. La configurazione Spring
![]() |
È possibile utilizzare il file di configurazione [AppConfig] del progetto precedente [spring-security-server-jdbc-generic]. È sufficiente aggiungere una configurazione supplementare:
@Configuration
@EnableWebSecurity
@EnableJpaRepositories(basePackages = { "spring.security.repositories" })
@ComponentScan(basePackages = { "spring.security.dao", "spring.security.service" })
@Import({ spring.webjson.server.config.AppConfig.class })
public class AppConfig extends WebSecurityConfigurerAdapter {
- riga 3: si dichiara il pacchetto che implementa il livello [repositories];
- riga 4: i pacchetti contenenti i bean Spring hanno lo stesso nome nel nuovo progetto;
- riga 5: nel progetto precedente, la classe [spring.webjson.server.config.AppConfig] si trovava nella dipendenza [spring-webjson-server-jdbc-generic]. Qui si troverà nella dipendenza [spring-webjson-server-jpa-generic];
20.4.3. Il livello JPA
![]() |
Le entità JPA gestite dal livello [JPA] si trovano nel progetto [mysql-config-jpa-hibernate] [2], che è una dipendenza del progetto [1]:
![]() |
La classe [User] è l'immagine della tabella [USERS]:

- ID: chiave primaria;
- VERSION: colonna di versioning della riga;
- IDENTITY: un'identità descrittiva dell'utente;
- LOGIN: nome utente;
- PASSWORD: la sua password;
package generic.jpa.entities.dbproduitscategories;
import generic.jdbc.config.ConfigJdbc;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = ConfigJdbc.TAB_USERS)
public class User implements AbstractCoreEntity {
// proprietà
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = ConfigJdbc.TAB_JPA_ID)
protected Long id;
@Version
@Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
protected Long version;
@Transient
protected EntityType entityType=EntityType.POJO;
// proprietà
@Column(name = ConfigJdbc.TAB_USERS_NAME, length = 30, nullable = false)
private String name;
@Column(name = ConfigJdbc.TAB_USERS_LOGIN, length = 30, unique = true, nullable = false)
private String login;
@Column(name = ConfigJdbc.TAB_USERS_PASSWORD, length = 60, nullable = false)
private String password;
// gli elementi associati UserRole
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
@JsonIgnore
private List<UserRole> userRoles;
// costruttori
public User() {
}
public User(Long id, Long version, String identity, String login, String password) {
this.id = id;
this.version = version;
this.name = identity;
this.login = login;
this.password = password;
}
// ------------------------------------------------------------
// ridefinizione di [equals] e [hashcode]
...
// getter e setter
...
}
- riga 23: la classe implementa l'interfaccia [AbstractCoreEntity] già utilizzata per le altre entità;
- righe 34-35: il tipo dell'entità. Questa proprietà non viene salvata nel database [@Transient];
- righe 38-43: le tre proprietà di base di un utente (name, login, password);
- righe 46-48: l'elenco dei ruoli dell'utente. Ne può avere diversi. Allo stesso modo, vedremo che a un ruolo possono essere associati più utenti. Si ha quindi, nel senso del termine JPA, una relazione [ManyToMany] tra le entità [User] e [Role]:
- un utente può essere associato a più ruoli;
- un ruolo può fare riferimento a più utenti;
Questa relazione [ManyToMany] è implementata nel database tramite la tabella di join [USERS_ROLES]. Se un utente U ha una relazione con un ruolo R, si inserisce questa relazione nella tabella [USERS_ROLES] registrando la coppia di chiavi primarie delle entità (U,R). Per quanto riguarda la tabella JPA, la relazione [ManyToMany] che collega le entità [User] e [Role] può essere suddivisa in due relazioni [ManyToOne, OneToMany]:
- (continua)
- una relazione [ManyToOne] dall’entità [User] all’entità [UserRole];
- una relazione [OneToMany] dall’entità [UserRole] all’entità [UserRole];
Allo stesso modo, la relazione [ManyToMany] che collega le entità [Role] e [User] può essere suddivisa in due relazioni [ManyToOne, OneToMany]:
- (continua)
- una relazione [ManyToOne] dall’entità [Role] all’entità [UserRole];
- una relazione [OneToMany] dall’entità [UserRole] all’entità [User];
- legame 48: il fatto che un utente abbia più ruoli è rappresentato da una relazione [OneToMany] verso l'entità [UserRole];
La classe [Role] è l'immagine della tabella [ROLES]:

- ID: chiave primaria;
- VERSION: colonna di versioning della riga;
- NAME: nome del ruolo. Per impostazione predefinita, Spring Security si aspetta nomi del tipo ROLE_XX, ad esempio ROLE_ADMIN o ROLE_GUEST;
package generic.jpa.entities.dbproduitscategories;
import generic.jdbc.config.ConfigJdbc;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = ConfigJdbc.TAB_ROLES)
public class Role implements AbstractCoreEntity {
// proprietà
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = ConfigJdbc.TAB_JPA_ID)
protected Long id;
@Version
@Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
protected Long version;
@Transient
protected EntityType entityType=EntityType.POJO;
// proprietà
@Column(name = ConfigJdbc.TAB_ROLES_NAME, length = 30, unique = true, nullable = false)
private String name;
// i UserRole associati
@OneToMany(fetch = FetchType.LAZY, mappedBy = "role", cascade = { CascadeType.ALL })
@JsonIgnore
private List<UserRole> userRoles;
// costruttori
public Role() {
}
public Role(Long id, Long version, String name) {
this.id = id;
this.version = version;
this.name = name;
}
// getter e setter
public Role(String name) {
this.name = name;
}
// ------------------------------------------------------------
// ridefinizione di [equals] e [hashcode]
...
// getter e setter
...
}
- righe 42-44: il fatto che a un ruolo possano essere associati più utenti si traduce in una relazione [@OneToMany] verso l’entità [UserRole];
La classe [UserRole] è l'immagine della tabella [USERS_ROLES]:

Un utente può avere più ruoli, mentre un ruolo può raggruppare più utenti. Si ha una relazione molti-a-molti rappresentata dalla tabella [USERS_ROLES].
- ID: chiave primaria;
- VERSION: colonna di versioning della riga;
- USER_ID: identificativo di un utente;
- ROLE_ID: identificativo di un ruolo;
package generic.jpa.entities.dbproduitscategories;
import generic.jdbc.config.ConfigJdbc;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;
@Entity
@Table(name = ConfigJdbc.TAB_USERS_ROLES)
public class UserRole implements AbstractCoreEntity {
// proprietà
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = ConfigJdbc.TAB_JPA_ID)
protected Long id;
@Version
@Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
protected Long version;
@Transient
protected EntityType entityType=EntityType.POJO;
// un UserRole fa riferimento a un utente
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_USER_ID, nullable = false)
private User user;
// un UserRole fa riferimento a un Role
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_ROLE_ID, nullable = false)
private Role role;
// costruttori
public UserRole() {
}
public UserRole(User user, Role role) {
this.user = user;
this.role = role;
}
// ------------------------------------------------------------
// ridefinizione di [equals] e [hashcode]
...
// getter e setter
...
}
- righe 34-36: definiscono la chiave esterna dalla tabella [USERS_ROLES] alla tabella [USERS];
- righe 38-41: definiscono la chiave esterna dalla tabella [USERS_ROLES] alla tabella [ROLES];
20.4.4. Il livello [repositories]
![]() |
![]() |
L'interfaccia [UserRepository] gestisce gli accessi alle entità [User]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
// elenco dei ruoli di un utente identificato dal proprio ID
@Query("select ur.role from UserRole ur where ur.user.id=?1")
Iterable<Role> getRoles(long id);
// elenco dei ruoli di un utente identificato dal proprio login univoco
@Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
Iterable<Role> getRoles(String login, String password);
// ricerca di un utente tramite il suo login
User findUserByLogin(String login);
}
- riga 9: l'interfaccia [UserRepository] estende l'interfaccia [CrudRepository] di Spring Data (riga 7);
- righe 12-13: il metodo [getRoles(long id)] consente di ottenere tutti i ruoli di un utente identificato tramite il suo [id]
- righe 16-17: lo stesso, ma per un utente identificato tramite login e password;
- riga 20: per trovare un utente tramite il suo nome utente;
L'interfaccia [RoleRepository] gestisce gli accessi alle entità [Role]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.Role;
import org.springframework.data.repository.CrudRepository;
public interface RoleRepository extends CrudRepository<Role, Long> {
// ricerca di un ruolo tramite il suo nome
Role findRoleByName(String name);
}
- riga 7: l'interfaccia [RoleRepository] estende l'interfaccia [CrudRepository];
- riga 10: è possibile cercare un ruolo tramite il suo nome. Si ricorda che l'entità [Role] dispone di un campo [name]. Il metodo [findEntityByChamp] viene implementato automaticamente da Spring Data. Non è quindi necessario implementare qui il metodo [finRoleByName]. È sufficiente dichiararlo nell'interfaccia.
L'interfaccia [UserRoleRepository] gestisce l'accesso alle entità [UserRole]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.UserRole;
import org.springframework.data.repository.CrudRepository;
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
}
- riga 7: l’interfaccia [UserRoleRepository] si limita a estendere l’interfaccia [CrudRepository] senza aggiungere nuovi metodi;
20.4.5. Il livello [DAO2]
![]() |
![]() |
Nel livello [DAO2] si ritrovano le stesse classi presenti nel livello [DAO2] del progetto [spring-security-server-jdbc-generic] esaminato in precedenza al paragrafo 20.2.2. Ora è sufficiente implementarle con l’ausilio delle classi del livello [repositories].
La classe [AppUserDetails] si evolve come segue:
package spring.security.dao;
import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import spring.data.infrastructure.DaoException;
import spring.security.repositories.UserRepository;
public class AppUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
// proprietà
private User user;
private UserRepository userRepository;
// locale
private String simpleClassName = getClass().getSimpleName();
// costruttori
public AppUserDetails() {
}
public AppUserDetails(User user, UserRepository userRepository) {
this.user = user;
this.userRepository = userRepository;
}
// -------------------------interfaccia
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
Iterable<Role> roles;
try {
roles = userRepository.getRoles(user.getId());
} catch (Exception e) {
e.printStackTrace();
throw new DaoException(167, e, simpleClassName);
}
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
...
}
- alla riga 31, il costruttore della classe riceve come secondo parametro l'oggetto [UserRepository], che consente alla classe di ottenere i ruoli di un determinato utente (riga 42);
Il componente Spring [AppUserDetailsService] si evolve invece come segue:
package spring.security.dao;
import generic.jpa.entities.dbproduitscategories.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import spring.data.infrastructure.DaoException;
import spring.security.repositories.UserRepository;
@Service
public class AppUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// locale
private String simpleClassName = getClass().getName();
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
// si cerca l'utente tramite il suo login
User user;
try {
user = userRepository.findUserByLogin(login);
} catch (Exception e) {
throw new DaoException(168, e, simpleClassName);
}
// trovato?
if (user == null) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// vengono visualizzati i dettagli dell'utente
return new AppUserDetails(user, userRepository);
}
}
- riga 18: iniezione del componente Spring [userRepository] che consentirà al servizio di restituire l’utente identificato tramite il suo login, riga 27;
Alla fine, ci si rende conto che è necessario solo il [userRepository] e non gli altri due repository [roleRepository, userRoleRepository]. Questi ultimi saranno utilizzati nel progetto successivo, che mira a popolare le tabelle [USERS, ROLES, USERS_ROLES].
20.4.6. I test
Il servizio web protetto viene avviato con la configurazione denominata [spring-security-server-jpa-generic-hibernate-eclipselink] [1]. Il test [JUnitTestDao] del client generico viene avviato con la configurazione denominata [spring-security-client-generic-JUnitTestDao] [2]:
![]() |
I test hanno esito positivo.
20.5. Il progetto Eclipse [spring-security-create-users]
![]() |
![]() |
20.5.1. Il database
L'esecuzione del progetto popola le tabelle [USERS, ROLES, USERS_ROLES] dalla tabella [dbproduitscategories]:

![]() |
![]() |
Gli identificativi creati [login/passwd] sono i seguenti: [admin/admin], [user/user], [guest/guest]. Nel database, le password sono crittografate.
![]() |

20.5.2. Configurazione Maven
Il progetto è un progetto Maven configurato dal seguente file [pom.xml]:
<?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>dvp.spring.database</groupId>
<artifactId>spring-security-create-users-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-security-create-users-jpa</name>
<description>création de utilisateurs dans la base [dbproduitscategories]</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring-security-server-jpa-generic -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-security-server-jpa-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- righe 22-25: dipendenza dal framework Spring Security. L'algoritmo di crittografia delle password è fornito da questo framework
- righe 27-31: dipendenza dal progetto [spring-security-server-jpa-generic] che abbiamo appena creato. Questo progetto implementa i livelli [repositories] e [JPA] del progetto;
In definitiva, le dipendenze sono le seguenti:
![]() |
20.5.3. Il livello [console]
![]() |
Poiché i livelli [repositories] e [JPA] sono implementati dalla dipendenza [spring-security-server-jpa-generic], resta da implementare solo il livello [console].
![]() |
- [AppConfig] è la classe di configurazione Spring del progetto;
- [CreateUsers] è la classe eseguibile che crea utenti e ruoli;
- [Base64Encoder] è una classe di supporto per generare il codice Base64 di una coppia [login, password]. L'abbiamo già utilizzata. Non è utile per questo progetto;
La classe di configurazione Spring [AppConfig] è la seguente:
package spring.security.install;
import generic.jpa.config.ConfigJpa;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration
@EnableJpaRepositories(basePackages = { "spring.security.repositories" })
@Import({ ConfigJpa.class })
public class AppConfig {
}
- riga 10: si indica dove trovare i [repositories] dell’applicazione. Si trovano nel pacchetto [spring.security.repositories] della dipendenza [spring-security-server-jpa-generic]
- riga 11: si importano i bean della classe [ConfigJpa] che configura il livello [JPA] del progetto. Questa classe si trova nella dipendenza [mysql-config-jpa-hibernate]:
![]() |
La classe [CreateUsers] è la seguente:
package spring.security.install;
import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;
import generic.jpa.entities.dbproduitscategories.UserRole;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.security.crypto.bcrypt.BCrypt;
import spring.security.repositories.RoleRepository;
import spring.security.repositories.UserRepository;
import spring.security.repositories.UserRoleRepository;
public class CreateUsers {
public static void main(String[] args) {
// fine
System.out.println("Travail en cours...");
// si creano tre utenti
String[] logins = { "admin", "user", "guest" };
String[] passwds = { "admin", "user", "guest" };
String[] roles = { "admin", "user", "guest" };
// contesto Spring
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserRepository userRepository = context.getBean(UserRepository.class);
RoleRepository roleRepository = context.getBean(RoleRepository.class);
UserRoleRepository userRoleRepository = context.getBean(UserRoleRepository.class);
for (int i = 0; i < logins.length; i++) {
// si recuperano le informazioni dell'utente n. i
String login = logins[i];
String password = passwds[i];
String roleName = String.format("ROLE_%s", roles[i].toUpperCase());
// il ruolo esiste già?
Role role = roleRepository.findRoleByName(roleName);
// se non esiste, lo si crea
if (role == null) {
role = roleRepository.save(new Role(roleName));
}
// L'utente esiste già?
User user = userRepository.findUserByLogin(login);
// se non esiste, lo si crea
if (user == null) {
// si esegue l'hash della password con bcrypt
String crypt = BCrypt.hashpw(password, BCrypt.gensalt());
// si salva l'utente
user = userRepository.save(new User(null, null, login, login, crypt));
// si crea il collegamento con il ruolo
userRoleRepository.save(new UserRole(user, role));
} else {
// l'utente esiste già: possiede il ruolo richiesto?
boolean trouvé = false;
for (Role r : userRepository.getRoles(user.getId())) {
if (r.getName().equals(roleName)) {
trouvé = true;
break;
}
}
// se non viene trovato, si crea la relazione con il ruolo
if (!trouvé) {
userRoleRepository.save(new UserRole(user, role));
}
}
}
// chiusura del contesto Spring
context.close();
// fine
System.out.println("Travail terminé...");
}
}
- righe 22-24: definiscono il nome utente, la password e il ruolo di tre utenti;
- riga 27: il contesto Spring viene creato a partire dalla classe di configurazione [AppConfig];
- righe 28-30: si recuperano i riferimenti delle tre classi [Repository] che possono essere utili per creare un utente;
- riga 31: si creano i tre utenti;
- righe 33-35: le informazioni per creare l'utente n. i;
- riga 37: si verifica se il ruolo esiste già;
- righe 39-41: se non esiste, lo si crea nel database. Avrà un nome del tipo [ROLE_XX];
- riga 43: si verifica se il login esiste già;
- righe 45-52: se il nome utente non esiste, lo si crea nel database;
- riga 47: si crittografa la password. Qui si utilizza la classe [BCrypt] di Spring Security (riga 8). Sono quindi necessari i file di questo framework. Il file [pom.xml] include questa dipendenza:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- riga 49: l'utente viene salvato nel database;
- riga 51: così come la relazione che lo collega al suo ruolo;
- righe 55-60: caso in cui il login esista già – si verifica quindi se tra i suoi ruoli sia già presente quello che si desidera assegnargli;
- righe 62-64: se il ruolo cercato non è stato trovato, si crea una riga nella tabella [USERS_ROLES] per collegare l'utente al suo ruolo;
- non ci si è protetti da eventuali eccezioni. Si tratta di una classe di supporto per creare rapidamente degli utenti;
Per eseguire il progetto, si eseguirà la configurazione di esecuzione denominata [spring-security-create-users-hibernate-eclipselink]:
![]() |
Abbiamo appena realizzato due servizi web sicuri:
- uno con l'architettura [security / web / JDBC / MySQL];
- l'altro con un'architettura [security / web / Hibernate / MySQL];
Passiamo ora ad altre due architetture:
- un'architettura [security / web / EclipseLink / SQL Server 2014 Express];
- un'architettura [security / web / OpenJpa / Oracle Express];
20.5.4. Architettura [security / web / EclipseLink / SQL Server]
![]() |
![]() |
- in [1], si caricano i progetti che configurano un livello [JDBC / SQL Server] e un livello [JPA / EclipseLink / SQL Server];
Nota: premere Alt-F5, quindi rigenerare tutti i progetti Maven.
Si presume che il server SGBD SQL sia in esecuzione e che il database [dbproduitscategories] sia stato generato. Per prima cosa è necessario popolare le tabelle [USERS, ROLES, USERS_ROLES] di questo database. A tal fine, eseguire la configurazione di esecuzione denominata [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
Questa configurazione deve compilare le tre tabelle con i dati:
![]() | ![]() |
![]() |
![]() |
- avviare il servizio web protetto con la configurazione denominata [spring-security-server-jpa-generic-hibernate-eclipselink][1];
- avviare il test JUnitTestDao con la configurazione denominata [spring-security-client-generic-JUnitTestDao][2]. Il test deve avere esito positivo [3];
![]() |
![]() |
20.5.5. Architettura [security / web / OpenJpa / Oracle Express]
![]() |
![]() |
- in [1], si caricano i progetti che configurano un livello [JDBC / Oracle Express] e un livello [JPA / OpenJpa / Oracle Express];
Nota: premere Alt-F5, quindi rigenerare tutti i progetti Maven.
Si suppone che Oracle Express SGBD sia in esecuzione e che il database [dbproduitscategories] sia stato generato. Per prima cosa è necessario popolare le tabelle [USERS, ROLES, USERS_ROLES] di questo database. A tal fine, eseguire la configurazione di esecuzione denominata [spring-security-create-users-openjpa]:
![]() | ![]() |
Questa configurazione deve compilare le tre tabelle con i dati:
![]() | ![]() |
![]() |
![]() |
- avviare il servizio web protetto con la configurazione denominata [spring-security-server-jpa-generic-openjpa][1-2];
- avviare il test JUnitTestDao con la configurazione denominata [spring-security-client-generic-JUnitTestDao][3]. Il test deve avere esito positivo [4];
![]() |
![]() |



























































































