Skip to content

20. Protezione del servizio web per l'accesso al database [dbproduitscategories]

20.1. Configurazione dell'ambiente di sviluppo

Implementeremo la sicurezza del servizio web utilizzando i seguenti progetti:

  
  • i progetti [spring-security-*] si trovano nella cartella [<examples>\spring-database-generic\spring-security];
  • La sicurezza verrà implementata per il DBMS MySQL utilizzando un livello [DAO / JDBC] seguito da un livello [DAO / JPA / Hibernate];
  • Premere Alt-F5 e quindi rigenerare tutti i progetti Maven;

Dobbiamo creare degli utenti nel database [dbproduitscategories]. Per farlo, usa la configurazione di esecuzione [spring-security-create-users-hibernate-eclipselink]:

L'esecuzione di questa configurazione popola le tabelle [USERS, ROLES, USERS_ROLES] nel database [dbproduitscategories]:

 

Le credenziali create [nome utente/password] sono le seguenti: [admin/admin], [user/user], [guest/guest]. Nel database, le password sono crittografate.

Image

Una volta fatto ciò, eseguire la configurazione di test denominata [spring-security-server-jpa-generic-hibernate-eclipselink], che avvia il servizio web protetto (MySQL deve essere in esecuzione):

Quindi eseguire la configurazione di test 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 sicuro è implementato dal progetto [spring-security-server-jdbc-generic]:

Sopra:

  • Il livello [DAO1] è il livello [DAO] che gestisce le tabelle [PRODUCTS] e [CATEGORIES] nel database [dbproduitscategories]. È già stato scritto;
  • Il livello [DAO2] è il livello [DAO] che gestisce le tabelle [USERS], [ROLES] e [USERS_ROLES] nel 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>
        <!-- web server / jSON -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-webjson-server-jdbc-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- plugins -->
    <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: riutilizziamo il codice esistente con l'archivio web service / json / jdbc che abbiamo esaminato;
  • righe 24–27: la dipendenza che introduce le classi Spring Security;

In definitiva, 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 [PRODUCTS] e [CATEGORIES] nel database [dbproduitscategories]. È già stato scritto;
  • Il livello [DAO2] è il livello [DAO] che gestisce le tabelle [USERS], [ROLES] e [USERS_ROLES] nel database [dbproduitscategories]. È quello 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;
    // properties
    private User user;
    private String simpleClassName = getClass().getSimpleName();
 
    // manufacturers
    public AppUserDetails() {
    }
 
    public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
        this.user = user;
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
    }
 
    // -------------------------interface
    @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;
    }
...
 
    // méthodes privées----------------
    private List<Role> getRoles(Long id) {
        try {
            // search for the user by 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);
        }
    }
 
}
 
// --------------------- mappers
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 fornisce i dettagli su quell'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 avviene. Viene fornito al costruttore nelle righe 36–39. Perché? Perché la classe [AppUserDetails] non è un componente Spring (manca l'annotazione @Component) e quindi non può essere iniettata;
  • 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 di un tipo derivato. Qui utilizziamo il tipo derivato [SimpleGrantedAuthority] (riga 46), che incapsula il nome di uno dei ruoli dell'utente della riga 29;
  • righe 45–47: iteriamo attraverso 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, utilizziamo il metodo privato [getRoles] della riga 53;
  • riga 56: esegue la seguente istruzione SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] (definita in [ConfigJdbc]):

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 recuperare i ruoli di un utente identificato dalla sua chiave primaria. È parametrizzata dalla chiave primaria [:id] dell'utente i cui ruoli vengono interrogati.

  • riga 56: ogni riga di risultato proveniente dal [SELECT] viene convertita in un'entità [Role] dalla classe [ShortRowMapper] nelle 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;
    // properties
    private User user;
    private String simpleClassName = getClass().getSimpleName();
 
    // manufacturers
    public AppUserDetails() {
    }
 
    public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
        this.user = user;
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
    }
 
    // -------------------------interface
    @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;
    }
 
    // getters and setters
    ...
}
  • righe 35–37: implementiamo il metodo [getPassword] dell'interfaccia [UserDetails]. Restituiamo la password dell'utente dalla riga 12;
  • righe 39–42: implementare il metodo [getUserName] dell'interfaccia [UserDetails]. Restituiamo 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 {
 
    // injections
    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        List<User> users;
        try {
            // search for user via login
            users = namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_USER_BYLOGIN,
                    Collections.singletonMap("login", login), new ShortUserMapper());
        } catch (Exception e) {
            throw new DaoException(145, e, simpleClassName);
        }
        // found?
        if (users.size() == 0) {
            throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
        }
        // render user details
        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] 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 in base al suo login. L'istruzione SQL [ConfigJdbc.SELECT_USER_BYLOGIN] è la 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 dall'istruzione SELECT viene convertita in un'entità [User] dalla classe [ShortUserMapper] nelle righe 52–58.

  • Righe 42–44: se non viene trovato, viene generata un'eccezione;
  • riga 46: viene costruito e restituito un oggetto [AppUserDetails]. È infatti 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 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à visto una classe di configurazione di Spring Security (vedere la Sezione 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 estende la classe [WebSecurityConfigurerAdapter];
  • riga 13: definire un metodo [configure(HttpSecurity http)] che definisca i diritti di accesso ai vari 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;
 
    // security
    private boolean activateSecurity = true;
 
    @Override
    protected void configure(AuthenticationManagerBuilder registry) throws Exception {
        // authentication is performed by bean [appUserDetailsService]
        // the password is encrypted using the BCrypt hash algorithm
        registry.userDetailsService(appUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // CSRF
        http.csrf().disable();
        // secure application?
        if (activateSecurity) {
            // the password is transmitted by the header Authorization: Basic xxxx
            http.httpBasic();
            // the HTTP OPTIONS method must be authorized for all
            http.authorizeRequests() //
                    .antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
            // only the ADMIN role can use the application
            http.authorizeRequests() //
                    .antMatchers("/", "/**") // all URL
                    .hasRole("ADMIN");
            // session or not?
            //http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    }
}
  • riga 17: la classe è una classe di configurazione Spring;
  • riga 18: abilita i componenti di Spring Security;
  • riga 26: recuperiamo i componenti Spring dal livello [DAO2] e quelli dal pacchetto [spring.security.service], di cui parleremo più avanti;
  • riga 23: importa i bean dal progetto [spring-webjson-server-jdbc-generic], che implementa il livello [web]. Tra questi bean ci sono anche quelli del livello [DAO1];
  • righe 22–23: viene iniettata la classe [AppUserDetails], che fornisce l'accesso agli utenti dell'applicazione;
  • riga 26: un valore booleano che protegge (true) o non protegge (false) l'applicazione web;
  • righe 28–33: il metodo [configure(HttpSecurity http)] definisce gli utenti e i loro ruoli. Accetta un [AuthenticationManagerBuilder] come parametro. Questo parametro è arricchito con due informazioni (riga 32):
    • un riferimento a [appUserDetailsService] dalla riga 23, che fornisce l'accesso agli utenti registrati. Si noti qui che il fatto che siano memorizzati in un database non è esplicitamente dichiarato. Potrebbero quindi trovarsi in una cache, essere forniti da un servizio web, ecc.
    • 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 agli URL del servizio web;
  • riga 38: abbiamo visto nel progetto introduttivo che, per impostazione predefinita, Spring Security gestisce un token CSRF (Cross-Site Request Forgery) che l'utente che desidera autenticarsi deve inviare al server. Qui, questo meccanismo è disabilitato. Questo, combinato con il valore booleano (isSecured=false), consente di utilizzare l'applicazione web senza sicurezza;
  • riga 42: abilitiamo l'autenticazione tramite header HTTP. Il client deve inviare il seguente header HTTP:
Authorization:Basic code

dove code è la codifica Base64 della stringa login:password. Ad esempio, la codifica Base64 della stringa admin:admin è YWRtaW46YWRtaW4=. Pertanto, un utente con login [admin] e password [admin] invierà la seguente intestazione HTTP per autenticarsi:

Authorization:Basic YWRtaW46YWRtaW4=
  • Righe 47–49: indicano che tutti gli URL del servizio web sono accessibili agli utenti con il ruolo [ROLE_ADMIN]. Ciò significa che un utente senza questo ruolo non può accedere al servizio web;
  • Riga 51: in modalità [session], un utente che si è autenticato una volta non ha bisogno di farlo per gli accessi successivi. Questa è l'impostazione predefinita per Spring Security. La riga 51 disabilita questa modalità. Se abilitata, l'utente deve autenticarsi ad ogni accesso. Senza una sessione, il servizio web sicuro è meno reattivo rispetto a quando è presente una sessione, quindi la riga 51 è stata commentata;

20.2.5. Test del servizio web protetto

Testeremo il servizio web utilizzando il client Chrome [Advanced Rest Client]. Dovremo specificare l'intestazione di autenticazione HTTP:

Authorization:Basic code

dove [code] è la stringa [login:password] codificata in Base64. Per generare questo codice, è possibile utilizzare il seguente programma dal 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) {
        // we expect two arguments: login password
        if (args.length != 2) {
            System.out.println("Syntaxe : login password");
            System.exit(0);
        }
        // we retrieve the two arguments
        String chaîne = String.format("%s:%s", args[0], args[1]);
        // encode the string
        byte[] data = Base64.encode(chaîne.getBytes());
        // displays its Base64 encoding
        System.out.println(new String(data));
    }
 
}

Se eseguiamo questo programma con i due argomenti [admin admin]:

  

otteniamo il seguente risultato:

YWRtaW46YWRtaW4=

Ora siamo pronti per il test:

  • il DBMS MySQL deve essere in esecuzione;
  • popoliamo le tabelle [PRODUCTS] e [CATEGORIES] utilizzando la configurazione di esecuzione denominata [spring-jdbc-generic-04-fillDataBase]:
 
  • se non è già stato fatto, popoliamo le tabelle [USERS, ROLES, USERS_ROLES] utilizzando la configurazione di esecuzione denominata [spring-security-create-users-hibernate-eclipselink]:
 
  • Avviamo il servizio web sicuro utilizzando la configurazione di runtime denominata [spring-security-server-jdbc-generic]:
 

Quindi, utilizzando il client Chrome [Advanced Rest Client], richiediamo la versione estesa di tutte le categorie:

  • In [1], richiediamo l'URL delle descrizioni estese delle categorie;
  • in [2], utilizzando un metodo GET;
  • in [3], forniamo l'intestazione HTTP di autenticazione. Il codice [YWRtaW46YWRtaW4=] è la codifica Base64 della stringa [admin:admin];
  • in [4], inviamo la richiesta HTTP;

La risposta del server è la seguente:

  • in [1], l'intestazione di autenticazione HTTP;
  • in [2], il server restituisce una risposta JSON;

Otteniamo con successo l'elenco delle categorie:

 

Ora proviamo a inviare una richiesta HTTP con un'intestazione di autenticazione errata. La risposta è la seguente:

  • in [1]: l'intestazione di autenticazione HTTP;

Riceviamo la seguente risposta:

  • in [2]: la risposta del servizio web;

Ora proviamo con l'utente / 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:

dXNlcjp1c2Vy
  • in [1]: l'intestazione di autenticazione HTTP errata;
  • in [2]: la risposta del servizio web. È diversa dalla precedente, che era [401 Non autorizzato]. Questa volta, l'utente si è autenticato correttamente ma non dispone delle autorizzazioni sufficienti per accedere all'URL;

Un servizio web sicuro è ora operativo.

20.2.6. Un URL di autenticazione

  

Creeremo un URL che ci consentirà di determinare se un utente è autorizzato 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 degli URL. L'annotazione [@RestController] indica che i metodi che gestiscono questi URL restituiscono le proprie risposte al client;
  • riga 11: espone l'URL [/authenticate];
  • righe 12–14: il metodo restituisce semplicemente un oggetto [Response] vuoto ma con uno [status] pari a 0, indicando che non si è verificato alcun errore;

A cosa serve questo URL? Quando vogliamo semplicemente autenticare un utente, lo richiederemo. Abbiamo visto che se il livello di sicurezza non accetta questo utente, genera un'eccezione. Ecco un esempio;

Con l'utente [admin:admin]:

Otteniamo una risposta vuota ma nessuna eccezione.

Con l'utente [user:user]:

Si è verificata un'eccezione.

20.2.7. Conclusione

Le classi necessarie per Spring Security sono state aggiunte senza modificare il progetto web/JSON originale. Questo scenario ideale deriva dal fatto che le tre tabelle aggiunte al database sono indipendenti da quelle esistenti. Avremmo potuto persino collocarle in un database separato. In altri casi, le tabelle aggiunte potrebbero avere relazioni con quelle esistenti. In tal caso, è necessario rivedere il codice nel livello [DAO] esistente.

20.3. Un client programmato per il servizio web/JSON sicuro

Abbiamo già scritto un client per il servizio web / JSON non protetto:

Ora creeremo un client programmato per il servizio web sicuro:

  

20.3.1. Il livello [Client HTTP]

 

La classe [Client] gestisce la comunicazione HTTP con il server web sicuro / JSON. Come abbiamo appena visto, in questa comunicazione HTTP, il client deve ora inviare un'intestazione di autenticazione, ad esempio:

Authorization:Basic YWRtaW46YWRtaW4=

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 {
 
    // properties
    private String login;
    private String password;
 
    // manufacturer
    public Credentials() {
    }
 
    public Credentials(String login, String password) {
        this.login = login;
        this.password = password;
    }
 
    // getters and setters
...
}

La classe [Client], che implementa l'interfaccia [IClient], si evolve come segue:


package spring.security.client.dao;
 
...
 
@Component
public class Client implements IClient {
 
    // injections
    @Autowired
    protected RestTemplate restTemplate;
    @Autowired
    protected String urlServiceWebJson;
 
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    private String getBase64(Credentials credentials) {
        // encodes user and password in base 64 - requires 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())));
    }
 
    // generic request
    @Override
    public <T1, T2> T1 getResponse(Credentials credentials, String url, HttpMethod method, int errStatus, T2 body) {
        // the server response
        ResponseEntity<Response<T1>> response;
        try {
            // prepare the query
            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);
            }
            // execute the query
            response = restTemplate.exchange(request, new ParameterizedTypeReference<Response<T1>>() {
            });
        } catch (Exception e) {
            // encapsulate the exception
            throw new DaoException(errStatus, e, simpleClassName);
        }
...
    }
...
}
  • righe 33–35, 40–42: se [credentials] dell'utente non è nullo, viene aggiunto l'header di autenticazione. La codifica Base64 del nome utente e della password è gestita dal metodo [getBase64] nelle righe 17–21. Si noti che questo metodo utilizza una classe [Base64] del JDK 1.8. Il nostro client HTTP può funzionare con un servizio web non protetto. È sufficiente passare un [credentials] pari 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] nel progetto [spring-webjson-client-generic] ricevono un parametro [Credentials] aggiuntivo:


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 {
 
    // list of all T entities
    public List<T> getAllShortEntities(Credentials credentials);
 
    public List<T> getAllLongEntities(Credentials credentials);
 
    // special entities - short version
    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);
 
    // special entities - long version
    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);
 
    // update of several entities
    public List<T> saveEntities(Credentials credentials, Iterable<T> entities);
 
    public List<T> saveEntities(Credentials credentials, @SuppressWarnings("unchecked") T... entities);
 
    // delete all entities
    public void deleteAllEntities(Credentials credentials);
 
    // deletion of multiple entities
    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 {
    // authentication
    public void authenticate(Credentials credentials);
}

L'interfaccia [IAuthenticate] ha un solo metodo, [authenticate]. Questo metodo non restituisce alcun valore (void) se l'utente [Credentials credentials] viene accettato dal servizio web sicuro; in caso contrario, genera un'eccezione.

20.3.2.2. La classe [AbstractDao]

  

Ricordiamo che la classe [AbstractDao] è la classe padre delle classi [DaoCategorie], che gestiscono gli URL delle categorie, e della classe [DaoProduit], che gestisce gli URL dei prodotti. Tutti i metodi della classe [AbstractDao] nel 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é de l'argument
        List<T1> entities = checkNullOrEmptyArgument(true, ids);
        if (entities != null) {
            return entities;
        }
        // résultat
        return getShortEntitiesById(credentials, Lists.newArrayList(ids));
}
  • Il metodo [getShortEntitiesById] riceve il parametro [Credentials] (riga 2), che trasmette (riga 9) al metodo [getShortEntitiesById] della classe figlia;

La classe [AbstractDao] presenta 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;
 
    // verification [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 la comunicazione con il servizio web sicuro;
  • righe 15–17: implementazione del metodo [authenticate] dell'interfaccia;
  • riga 16: viene inviata una richiesta HTTP GET all'URL [/authenticate]. L'uso di questo URL è stato illustrato nella sezione 20.2.6. Il principio è che la chiamata genera un'eccezione se le [credenziali] dell'utente sono sconosciute o non dispongono di autorizzazioni sufficienti;

La classe [AbstractDao] implementa il metodo [authenticate] dell'interfaccia [IDao] come segue:


    @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]. Verrà quindi generata un'eccezione se l'utente [Credentials credentials] non viene accettato dal servizio web sicuro;

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> {
 
    // injections
    @Autowired
    protected ApplicationContext context;
    @Autowired
    protected IClient client;
 
    @Override
    public List<Categorie> getAllShortEntities(Credentials credentials) {
        try {
            // filters jSON
            ObjectMapper mapper = context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
            // get all categories
            Object map = client.<List<Categorie>, Void> getResponse(credentials, "/getAllShortCategories", HttpMethod.GET,
                    202, null);
            // the List<Categorie> category list
            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], con un'unica eccezione:


@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
  • riga 2: è necessario specificare il pacchetto del nuovo livello [DAO];

20.3.4. Test per il livello [DAO]

  

20.3.4.1. Il test [JUnitTestCredentials]

Il test [JUnitTestCredentials] utilizza il metodo [IDao.authenticate] per verificare la validità 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 {

    // layer [DAO]
    @Autowired
    private IAuthenticate authenticate;
 
    // users
    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, alle righe 29–34, vengono creati tre utenti:
    • l'utente [admin] ha accesso agli URL del servizio web. Questo viene verificato alle righe 63–72;
    • l'utente [user] esiste ma non è autorizzato a utilizzare gli URL del servizio web. Viene verificato alle righe 37–47;
    • l'utente [unknown] non esiste. Viene testato alle righe 50–60;

Avviamo il servizio web sicuro con la configurazione di runtime denominata [spring-security-server-jdbc-generic] [1]:

Eseguiamo quindi il test JUnit [JUnitTestCredentials] con la configurazione di runtime [spring-security-client-generic-JUnitTestCredentials] [2]. I risultati ottenuti in console sono i seguenti:

checkUserUser: [code=111, trace=[Client,getResponse,65], exceptions=[{"className":"org.springframework.web.client.HttpClientErrorException","errorMessage":"403 Forbidden"}]
checkUserUnknown : [code=111, trace=[Client,getResponse,65], exceptions=[{"className":"org.springframework.web.client.HttpClientErrorException","errorMessage":"401 Unauthorized"}]

e il test viene superato:

  

20.3.4.2. Il test [JUnitTestDao]

Il test [JUnitTestDao] è identico a quello presente nel progetto [spring-webjson-client-generic] non protetto, tranne per il fatto che ora i metodi del livello [DAO] sottoposti a test hanno tutti l'utente [admin / admin] come primo parametro:


@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestDao {
 
    // spring context
    @Autowired
    private ApplicationContext context;
    // layer [DAO]
    @Autowired
    private IDao<Produit> daoProduit;
    @Autowired
    private IDao<Categorie> daoCategorie;
 
....
 
    // users
    static private Credentials admin;
 
    @BeforeClass
    public static void init() {
        admin = new Credentials("admin", "admin");
    }
 
    @Before
    public void clean() {
        // the base is cleaned before each test
        log("Vidage de la base de données", 1);
        // we empty table [CATEGORIES] and cascade table [PRODUITS]
        daoCategorie.deleteAllEntities(admin);
        // emptying dictionaries
        for (Long id : mapCategories.keySet()) {
            mapCategories.remove(id);
        }
        for (Long id : mapProduits.keySet()) {
            mapProduits.remove(id);
        }
    }
 
    private List<Categorie> fill(int nbCategories, int nbProduits) {
        // fill the tables
        ...
        // adding the category - by cascading the products will also be
        // inserted - the result is returned at the same time
        return daoCategorie.saveEntities(admin, categories);
    }
 
    private Object[] showDataBase() throws BeansException, JsonProcessingException {
        // list of categories
        log("Liste des catégories", 2);
        List<Categorie> categories = daoCategorie.getAllShortEntities(admin);
        affiche(categories, context.getBean("jsonMapperShortCategorie", ObjectMapper.class));
        // product list
        log("Liste des produits", 2);
        List<Produit> produits = daoProduit.getAllShortEntities(admin);
        affiche(produits, context.getBean("jsonMapperShortProduit", ObjectMapper.class));
        // result
        return new Object[] { categories, produits };
    }
...

Tutte le operazioni vengono eseguite utilizzando l'utente [admin / admin], l'unico con diritti di accesso al servizio web protetto.

Eseguiremo il test utilizzando la configurazione di esecuzione denominata [spring-security-client-generic-JUnitTestDao]:

 

Il test viene superato, ma possiamo notare che è più lento rispetto al servizio web non protetto. La protezione di un'applicazione aumenta significativamente i suoi tempi di risposta. C'è un fattore importante che influisce sulle 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();
        // secure application?
        if (activateSecurity) {
            // the password is transmitted by the header Authorization: Basic xxxx
            http.httpBasic();
            // the HTTP OPTIONS method must be authorized for all
            http.authorizeRequests() //
                    .antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
            // only the ADMIN role can use the application
            http.authorizeRequests() //
                    .antMatchers("/", "/**") // all URL
                    .hasRole("ADMIN");
            // no session
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
}

La riga 17 ha un impatto. Determina se l'utente è costretto o meno ad autenticarsi ad ogni accesso. Se la commentiamo, la durata del test JUnit è significativamente più breve, 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 sicuro sarà ora implementato dal progetto [spring-security-server-jpa-generic], che si basa sul progetto [spring-jpa-generic] che gestisce l'accesso al database utilizzando Spring Data JPA:

Sopra:

  • il livello [DAO1] è il livello [DAO] che gestisce le tabelle [PRODUCTS] e [CATEGORIES] nel database [dbproduitscategories]. È già stato scritto;
  • il livello [DAO2] è il livello [DAO] che gestisce le tabelle [USERS], [ROLES] e [USERS_ROLES] nel database [dbproduitscategories]. Deve ancora essere scritto;

Il progetto [spring-security-server-jpa-generic] viene inizialmente creato clonando il progetto precedentemente studiato [spring-security-server-jdbc-generic]. Infatti, i livelli [web] e [security] rimangono invariati 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>
        <!-- web server / jSON -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-webjson-server-jpa-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- plugins -->
    <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 per il 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]. Questo livello non deve essere scritto o modificato;

Alla fine, le dipendenze sono le seguenti:

  

20.4.2. La configurazione di Spring

  

Il file di configurazione [AppConfig] del progetto precedente [spring-security-server-jdbc-generic] è adatto. È sufficiente aggiungere una configurazione aggiuntiva:


@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: dichiariamo 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] rappresenta la mappatura della tabella [USERS]:

Image

  • ID: chiave primaria;
  • VERSION: colonna di versioning delle righe;
  • IDENTITY: un identificatore descrittivo per l'utente;
  • LOGIN: login dell'utente;
  • PASSWORD: la password dell'utente;

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 {
    // properties
    @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;
 
    // properties
    @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;
 
    // the associated UserRole
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
    @JsonIgnore
    private List<UserRole> userRoles;
 
    // manufacturers
    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;
    }
 
    // ------------------------------------------------------------
    // redefine [equals] and [hashcode]
...
 
    // getters and setters
...
}
  • riga 23: la classe implementa l'interfaccia [AbstractCoreEntity] già utilizzata per le altre entità;
  • righe 34–35: il tipo di entità. Questa proprietà non viene salvata nel database [@Transient];
  • righe 38–43: le tre proprietà di base di un utente (nome, login, password);
  • righe 46–48: l'elenco dei ruoli dell'utente. Un utente può avere più ruoli. Allo stesso modo, vedremo che un ruolo può essere associato a più utenti. Pertanto, nel senso JPA del termine, esiste una relazione [ManyToMany] tra le entità [User] e [Role]:
    • Un utente può essere assegnato a più ruoli;
    • Un ruolo può essere associato 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, tale relazione viene memorizzata nella tabella [USERS_ROLES] registrando la coppia di chiavi primarie delle entità (U,R). Dal punto di vista 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];
  • riga 48: il fatto che un utente abbia più ruoli è rappresentato da una relazione [OneToMany] con l'entità [UserRole];

La classe [Role] rappresenta la tabella [ROLES]:

Image

  • ID: chiave primaria;
  • VERSION: colonna di versioning delle righe;
  • NAME: nome del ruolo. Per impostazione predefinita, Spring Security si aspetta nomi nel formato 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 {
    // properties
    @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;
 
    // properties
    @Column(name = ConfigJdbc.TAB_ROLES_NAME, length = 30, unique = true, nullable = false)
    private String name;
 
    // the associated UserRole
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "role", cascade = { CascadeType.ALL })
    @JsonIgnore
    private List<UserRole> userRoles;
 
    // manufacturers
    public Role() {
    }
 
    public Role(Long id, Long version, String name) {
        this.id = id;
        this.version = version;
        this.name = name;
    }
 
    // getters and setters
    public Role(String name) {
        this.name = name;
    }
 
    // ------------------------------------------------------------
    // redefine [equals] and [hashcode]
...
    // getters and setters
...
}
  • righe 42–44: il fatto che più utenti possano essere associati a un ruolo è rappresentato da una relazione [@OneToMany] con l'entità [UserRole];

La classe [UserRole] rappresenta la tabella [USERS_ROLES]:

Image

Un utente può avere più ruoli e un ruolo può avere più utenti. Abbiamo una relazione molti-a-molti rappresentata dalla tabella [USERS_ROLES].

  • ID: chiave primaria;
  • VERSION: colonna di versioning delle righe;
  • USER_ID: identificatore utente;
  • ROLE_ID: identificatore 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 {
    // properties
    @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;
 
    // a UserRole refers to a User
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_USER_ID, nullable = false)
    private User user;
 
    // a UserRole refers to a Role
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_ROLE_ID, nullable = false)
    private Role role;
 
    // manufacturers
    public UserRole() {
 
    }
 
    public UserRole(User user, Role role) {
        this.user = user;
        this.role = role;
    }
 
    // ------------------------------------------------------------
    // redefine [equals] and [hashcode]
    ...
 
    // getters and setters
...
}
  • righe 34–36: implementare la chiave esterna dalla tabella [USERS_ROLES] alla tabella [USERS];
  • righe 38–41: implementare la chiave esterna dalla tabella [USERS_ROLES] alla tabella [ROLES];

20.4.4. Il livello [repositories]

  

L'interfaccia [UserRepository] gestisce l'accesso 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> {
 
    // liste des rôles d'un utilisateur identifié par son id
    @Query("select ur.role from UserRole ur where ur.user.id=?1")
    Iterable<Role> getRoles(long id);
 
    // liste des rôles d'un utilisateur identifié par son login unique
    @Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
    Iterable<Role> getRoles(String login, String password);
 
    // recherche d'un utilisateur via son 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)] recupera tutti i ruoli per un utente identificato dal proprio [id]
  • righe 16-17: come sopra, ma per un utente identificato dal proprio login e password;
  • Riga 20: per trovare un utente tramite il suo nome utente;

L'interfaccia [RoleRepository] gestisce l'accesso 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> {
 
    // search for a role by name
    Role findRoleByName(String name);
 
}
  • riga 7: l'interfaccia [RoleRepository] estende l'interfaccia [CrudRepository];
  • riga 10: È possibile cercare un ruolo in base al suo nome. Si noti che l'entità [Role] ha un campo [name]. Il metodo [findEntityByField] viene implementato automaticamente da Spring Data. Pertanto, non è necessario implementare qui il metodo [findRoleByName]. È 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] estende semplicemente l'interfaccia [CrudRepository] senza aggiungere alcun nuovo metodo;

20.4.5. Il livello [DAO2]

  

Il livello [DAO2] contiene le stesse classi del livello [DAO2] nel progetto [spring-security-server-jdbc-generic] descritto in precedenza nella Sezione 20.2.2. Ora, dobbiamo semplicemente implementarle utilizzando le 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;
 
    // properties
    private User user;
    private UserRepository userRepository;
    
    // local
    private String simpleClassName = getClass().getSimpleName();
 
    // manufacturers
    public AppUserDetails() {
    }
 
    public AppUserDetails(User user, UserRepository userRepository) {
        this.user = user;
        this.userRepository = userRepository;
    }
 
    // -------------------------interface
    @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 l'oggetto [UserRepository] come secondo parametro, il che consente alla classe di recuperare i ruoli di un determinato utente (riga 42);

Il componente Spring [AppUserDetailsService] si evolve 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;
    // local
    private String simpleClassName = getClass().getName();
 
    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        // search for user via login
        User user;
        try {
            user = userRepository.findUserByLogin(login);
        } catch (Exception e) {
            throw new DaoException(168, e, simpleClassName);
        }
        // found?
        if (user == null) {
            throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
        }
        // render user details
        return new AppUserDetails(user, userRepository);
    }
 
}
  • riga 18: iniezione del componente Spring [userRepository], che consentirà al servizio di restituire l'utente identificato dal suo login, riga 27;

In definitiva, ci rendiamo conto che abbiamo bisogno solo di [userRepository] e non degli altri due repository [roleRepository, userRoleRepository]. Questi saranno utilizzati nel prossimo progetto, che mira a popolare le tabelle [USERS, ROLES, USERS_ROLES].

20.4.6. Test

Il servizio web sicuro viene avviato con la configurazione denominata [spring-security-server-jpa-generic-hibernate-eclipselink] [1]. Il test [JUnitTestDao] per il client generico viene avviato con la configurazione denominata [spring-security-client-generic-JUnitTestDao] [2]:

I test vengono superati.

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] nel database [dbproduitscategories]:

Image

 

Le credenziali create [nome utente/password] sono le seguenti: [admin/admin], [user/user], [guest/guest]. Per impostazione predefinita, le password sono crittografate.

Image

20.5.2. Configurazione di 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;

Alla fine, 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 per una coppia [login, password]. L'abbiamo già utilizzata. Non è necessaria 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: Specifica dove trovare i [repository] dell'applicazione. Si trovano nel pacchetto [spring.security.repositories] della dipendenza [spring-security-server-jpa-generic]
  • Riga 11: Importa i bean dalla 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) {
 
        // end
        System.out.println("Travail en cours...");
 
        // we create three users
        String[] logins = { "admin", "user", "guest" };
        String[] passwds = { "admin", "user", "guest" };
        String[] roles = { "admin", "user", "guest" };
 
        // spring context
        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++) {
            // we retrieve information from user n° i
            String login = logins[i];
            String password = passwds[i];
            String roleName = String.format("ROLE_%s", roles[i].toUpperCase());
            // does the role already exist?
            Role role = roleRepository.findRoleByName(roleName);
            // if it doesn't exist, we create it
            if (role == null) {
                role = roleRepository.save(new Role(roleName));
            }
            // does the user already exist?
            User user = userRepository.findUserByLogin(login);
            // if it doesn't exist, we create it
            if (user == null) {
                // hash the password with bcrypt
                String crypt = BCrypt.hashpw(password, BCrypt.gensalt());
                // save user
                user = userRepository.save(new User(null, null, login, login, crypt));
                // we create the relationship with the role
                userRoleRepository.save(new UserRole(user, role));
            } else {
                // the user already exists - does he/she have the required role?
                boolean trouvé = false;
                for (Role r : userRepository.getRoles(user.getId())) {
                    if (r.getName().equals(roleName)) {
                        trouvé = true;
                        break;
                    }
                }
                // if not found, we create the relationship with the role
                if (!trouvé) {
                    userRoleRepository.save(new UserRole(user, role));
                }
            }
        }
        // closing Spring context
        context.close();
        // end
        System.out.println("Travail terminé...");
    }
 
}
  • righe 22–24: definiscono il nome utente, la password e il ruolo per tre utenti;
  • riga 27: il contesto Spring viene creato dalla classe di configurazione [AppConfig];
  • righe 28–30: recuperano i riferimenti ai tre oggetti [Repository] che possono essere utilizzati per creare un utente;
  • riga 31: crea i tre utenti;
  • righe 33–35: informazioni per creare l'utente #i;
  • riga 37: si verifica se il ruolo esiste già;
  • righe 39–41: in caso contrario, lo creiamo nel database. Avrà un nome del tipo [ROLE_XX];
  • riga 43: controlliamo se il login esiste già;
  • righe 45–52: se il nome utente non esiste, lo creiamo nel database;
  • riga 47: crittografiamo la password. Qui utilizziamo la classe [BCrypt] di Spring Security (riga 8). Abbiamo quindi bisogno degli archivi per 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: se l'account esiste già, verifichiamo se il ruolo che vogliamo assegnargli è già tra i suoi ruoli;
  • righe 62–64: se il ruolo cercato non viene trovato, viene creata una riga nella tabella [USERS_ROLES] per collegare l'utente al proprio ruolo;
  • Non abbiamo previsto alcuna protezione contro potenziali eccezioni. Si tratta di una classe di supporto per la creazione rapida di utenti;

Per eseguire il progetto, eseguire la configurazione di esecuzione denominata [spring-security-create-users-hibernate-eclipselink]:

 

Abbiamo appena creato due servizi web sicuri:

  • uno con un'architettura [sicurezza / web / JDBC / MySQL];
  • l'altro con un'architettura [sicurezza / web / Hibernate / MySQL];

Esamineremo ora altre due architetture:

  • un'architettura [sicurezza / web / EclipseLink / SQL Server 2014 Express];
  • un'architettura [sicurezza / web / OpenJpa / Oracle Express];

  • In [1], carichiamo i progetti configurando 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 DBMS SQL Server sia in esecuzione e che il database [dbproduitscategories] sia stato generato. Innanzitutto, è necessario popolare le tabelle [USERS, ROLES, USERS_ROLES] in questo database. A tal fine, eseguire la configurazione di esecuzione denominata [spring-security-create-users-hibernate-eclipselink]:

Questo dovrebbe popolare le tre tabelle con i dati:

 
 
  • Avvia il servizio web sicuro utilizzando la configurazione denominata [spring-security-server-jpa-generic-hibernate-eclipselink][1];
  • eseguire il test JUnitTestDao con la configurazione denominata [spring-security-client-generic-JUnitTestDao][2]. Dovrebbe superare il test [3];

20.5.5. Architettura [sicurezza / web / OpenJpa / Oracle Express]

  • In [1], caricare i progetti configurando un livello [JDBC / Oracle Express] e un livello [JPA / OpenJpa / Oracle Express];

Nota: premere Alt-F5, quindi rigenerare tutti i progetti Maven.

Si presume che il DBMS Oracle Express sia in esecuzione e che il database [dbproduitscategories] sia stato generato. Innanzitutto, è necessario popolare le tabelle [USERS, ROLES, USERS_ROLES] in questo database. A tal fine, eseguire la configurazione di esecuzione denominata [spring-security-create-users-openjpa]:

Questo dovrebbe popolare le tre tabelle con i dati:

 
 
  • Avvia il servizio web sicuro utilizzando la configurazione denominata [spring-security-server-jpa-generic-openjpa][1-2];
  • eseguire il test JUnitTestDao con la configurazione denominata [spring-security-client-generic-JUnitTestDao][3]. Dovrebbe superare il test [4];