Skip to content

20. Securing the web service for database access [dbproduitscategories]

20.1. Setting up the development environment

We will implement web service security using the following projects:

  
  1. The [spring-security-*] projects can be found in the [<exemples>\spring-database-generic\spring-security] folder;
  2. Security will be implemented for SGBD and MySQL with a [DAO / JDBC] layer followed by a [DAO / JPA / Hibernate] layer;
  3. Press Alt-F5, then regenerate all Maven projects;

We need to create users in the [dbproduitscategories] database. To do this, use the [spring-security-create-users-hibernate-eclipselink] run configuration:

Running this configuration populates the [USERS, ROLES, USERS_ROLES] tables from the [dbproduitscategories] table:

 

The user IDs created by [login/passwd] are as follows: [admin/admin], [user/user], [guest/guest]. In the database, the passwords are encrypted.

Image

Once this is done, run the execution configuration named [spring-security-server-jpa-generic-hibernate-eclipselink], which launches the secure web service (MySQL must be running):

Then run the execution configuration named [spring-security-client-generic-JUnitTestDao], which tests the secure web service:

The test should succeed.

20.2. The Eclipse project [spring-security-server-jdbc-generic]

The secure web service is implemented by the [spring-security-server-jdbc-generic] project:

Above:

  • The [DAO1] layer is the [DAO] layer that manages the [PRODUITS] and [CATEGORIES] tables in the [dbproduitscategories] database. It has already been written;
  • the layer [DAO2] is the layer [DAO] that manages the tables [USERS], [ROLES], and [USERS_ROLES] from the [dbproduitscategories] database. It has yet to be written;

  

20.2.1. The Maven configuration

The [spring-security-server-jdbc-generic] project is a Maven project configured by the following [pom.xml] file:


<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>
  • lines 29–33: we reuse the existing code with the web service archive / json / jdbc that we examined;
  • lines 24–27: the dependency that brings in the Spring Security classes;

Ultimately, the project has the following dependencies on the other projects loaded in Eclipse:

  

20.2.2. The [DAO2] layer

Above:

  1. The [DAO1] layer is the [DAO] layer that manages the [PRODUITS] and [CATEGORIES] tables in the [dbproduitscategories] database. It has already been written;
  2. the [DAO2] layer is the [DAO] layer that manages the [USERS], [ROLES], and [USERS_ROLES] from the [dbproduitscategories] database. This is the one we are going to write now;
  

Spring Security requires the creation of a class that implements the following [UsersDetail] interface:

 

This interface is implemented here by the class [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 {
            // we search for the user via his 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"));
    }
}
  1. line 22: the class [AppUserDetails] implements the interface [UserDetails];
  2. lines 29–30: the class encapsulates a user (line 19) and the repository that provides details about that user (line 20);
  3. line 27: the database will be accessed via JDBC using the [NamedParameterJdbcTemplate namedParameterJdbcTemplate] object defined in the [spring-jdbc-generic-04] project. Note that this object is not injected by Spring as is often done. It is provided to the constructor in lines 36–39. Why? Because the [AppUserDetails] class is not a Spring component (it lacks the @Component annotation) and therefore cannot be injected;
  4. lines 36–39: the constructor that instantiates the class with a user and its repository;
  5. Lines 42–49: Implementation of the [getAuthorities] method of the [UserDetails] interface. It must construct a collection of elements of type [GrantedAuthority] or a derived type. Here, we use the derived type [SimpleGrantedAuthority] (line 46), which encapsulates the name of one of the user’s roles from line 29;
  6. lines 45–47: we iterate through the list of the user’s roles from line 29 to build a list of elements of type [SimpleGrantedAuthority];
  7. line 45: to obtain the user’s roles, the private method [getRoles] from line 53 is used;
  8. line 56: executes the following SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] command (defined 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";

This SQL query performs a join between the three [USERS, ROLES, USERS_ROLES] tables to retrieve the roles of a user identified by their primary key. It is parameterized by the primary key [:id] of the user whose roles are being searched for.

  • Line 56: Each result row from [SELECT] is converted into a [Role] entity by the [ShortRowMapper] class in lines 66–72;

Let’s return to the code for the [AppUserDetails] class:


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
    ...
}
  • lines 35–37: implement the [getPassword] method of the [UserDetails] interface. The user’s password from line 12 is returned;
  • lines 39–42: implement the [getUserName] method of the [UserDetails] interface. The user’s login from line 12 is returned;
  • lines 44–47: the user’s account never expires;
  • lines 49–52: the user’s account is never locked;
  • lines 54–57: the user’s credentials never expire;
  • lines 59-62: the user's account is always active;

Spring Security also requires the existence of a class that implements the [AppUserDetailsService] interface:

 

This interface is implemented by the following [AppUserDetailsService] class:


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"));
    }
}
  1. line 21: the class will be a Spring component;
  2. lines 25-26: the database will be accessed via JDBC using the [NamedParameterJdbcTemplate namedParameterJdbcTemplate] object defined in the beans of the [spring-jdbc-generic-04] project;
  3. lines 31–49: implementation of the [loadUserByUsername] method of the [UserDetailsService] interface (line 22). The parameter is the user’s login;
  4. lines 36-37: the user is searched for by their login. The order SQL [ConfigJdbc.SELECT_USER_BYLOGIN] is as follows:

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";

Each row returned by SELECT is converted into a [User] entity by the [ShortUserMapper] class in lines 52–58.

  • lines 42–44: if it is not found, an exception is thrown;
  • line 46: a [AppUserDetails] object is constructed and rendered. It is indeed of type [UserDetails] (line 32). Two pieces of information are passed to its constructor:
    • the user that was found;
    • the [namedParameterJdbcTemplate] object that will allow the [AppUserDetails] class to query the database;

20.2.3. The [web] layer

The [spring-security-server-jdbc-generic] project depends on the [spring-webjson-server-jdbc-generic] project:

  

This project implements the [web] layer. It does not need to be modified.

20.2.4. Project security configuration

The project is configured by the following [AppConfig] class:

1
  

We have already encountered a Spring Security configuration class (see Section 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");
    }
}

We will follow the same procedure:

  1. line 11: define a class that extends the [WebSecurityConfigurerAdapter] class;
  2. line 13: define a method [configure(HttpSecurity http)] that defines access rights to the various URL methods of the web service;
  3. line 19: define a method [configure(AuthenticationManagerBuilder auth)] that defines users and their roles;

The [AppConfig] class will be as follows:


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 the [appUserDetailsService] bean
        // 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);
        }
    }
}
  • line 17: the class is a Spring configuration class;
  • line 18: enables Spring Security components;
  • line 26: we retrieve the Spring components from the [DAO2] layer and those from the [spring.security.service] package, which we will discuss later;
  • line 23: imports the beans from the [spring-webjson-server-jdbc-generic] project, which implements the [web] layer. Among these beans are also those from the [DAO1] layer;
  • lines 22–23: the [AppUserDetails] class, which provides access to application users, is injected;
  • line 26: a Boolean that secures (true) or does not secure (false) the web application;
  • lines 28–33: the [configure(HttpSecurity http)] method defines users and their roles. It receives a [AuthenticationManagerBuilder] type as a parameter. This parameter is enriched with two pieces of information (line 32):
    • a reference to the [appUserDetailsService] service from line 23, which provides access to registered users. Note here that the fact that they are stored in a database is not explicitly stated. They could therefore be stored in a cache, delivered by a web service, etc.
    • the type of encryption used for the password. We used the BCrypt algorithm;
  • lines 35–53: the [configure(HttpSecurity http)] method defines access rights to the URL tokens of the web service;
  • line 38: we saw in the introductory project that by default Spring Security manages a CSRF token (Cross-Site Request Forgery) that the user wishing to authenticate must send back to the server. Here, this mechanism is disabled. This, combined with the boolean setting (isSecured=false), allows the web application to be used without security;
  • Line 42: We enable authentication via the HTTP header. The client must send the following HTTP header:
Authorization:Basic code

where code is the Base64 encoding of the login:password string. For example, the Base64 encoding of the string admin:admin is YWRtaW46YWRtaW4=. Therefore, a user with the login [admin] and password [admin] will send the following header HTTP to authenticate:

Authorization:Basic YWRtaW46YWRtaW4=
  • Lines 47–49: indicate that all URL resources of the web service are accessible to users with the [ROLE_ADMIN] role. This means that a user without this role cannot access the web service;
  • Line 51: In [session] mode, a user who has authenticated once does not need to do so for subsequent accesses. This is the default setting for Spring Security. Line 51 disables this mode. If enabled, the user will need to authenticate with every access. Without a session, the secure web service is less responsive than with a session, so line 51 has been commented out;

20.2.5. Testing the secure web service

We will test the web service using the Chrome client [Advanced Rest Client]. We will need to specify the HTTP authentication header:

Authorization:Basic code

where [code] is the Base64-encoded string [login:password]. To generate this code, you can use the following program from the [spring-security-create-users] project:

  

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));
    }
 
}

If we run this program with the two arguments [admin admin]:

  

we get the following result:

YWRtaW46YWRtaW4=

We are now ready for testing:

  • SGBD and MySQL must be launched;
  • we populate the tables [PRODUITS] and [CATEGORIES] with the execution configuration named [spring-jdbc-generic-04-fillDataBase]:
 
  1. if this has not already been done, we populate the tables [USERS, ROLES, USERS_ROLES] with the execution configuration named [spring-security-create-users-hibernate-eclipselink]:
 
  • We launch the secure web service with the execution configuration named [spring-security-server-jdbc-generic]:
 

Then, using the Chrome client [Advanced Rest Client], we request the long version for all categories:

  1. in [1], we request the URL for the long categories;
  2. in [2], using a GET method;
  3. in [3], we provide the HTTP header for authentication. The code [YWRtaW46YWRtaW4=] is the Base64 encoding of the string [admin:admin];
  4. In [4], we send the command HTTP;

The server's response is as follows:

  • in [1], the authentication header HTTP;
  • In [2], the server returns a response jSON;

We successfully obtain the list of categories:

 

Now let’s try a HTTP request with an incorrect authentication header. The response is then as follows:

  1. in [1]: the authentication header HTTP;

We receive the following response:

  • in [2]: the web service response;

Now, let’s try the user / user. It exists but does not have access to the web service. If we run the Base64 encoding program with the two arguments [user user]:

  

we get the following result:

dXNlcjp1c2Vy
  • in [1]: the incorrect authentication header HTTP;
  1. in [2]: the web service response. It differs from the previous one, which was [401 Unauthorized]. This time, the user authenticated successfully but does not have sufficient permissions to access URL;

A secure web service is now operational.

20.2.6. An authentication URL

  

We will create a URL that will allow us to determine whether a user is authorized to access the web service. To do this, we create the following new MVC [AuthenticateController] controller:


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);
    }
 
}
  • Line 9: The [AuthenticateController] class is a Spring controller. As such, it exposes URL. The [@RestController] annotation indicates that the methods handling these URL return their own responses to the client;
  • line 11: exposes the URL [/authenticate];
  • lines 12–14: the method simply returns an empty [Response] object but with a [status] equal to 0, indicating that no error occurred;

What is this URL used for? When we simply want to authenticate a user, we will request it. We have seen that if the security layer does not accept this user, it throws an exception. Here is an example;

With the user [admin:admin]:

We get an empty response but no exception.

With the user [user:user]:

We got an exception.

20.2.7. Conclusion

The necessary classes were added to Spring Security without modifying the original web project / json. This very favorable scenario stems from the fact that the three tables added to the database are independent of the existing tables. We could even have placed them in a separate database. In other cases, the added tables may have relationships with existing tables. The code of the existing [DAO] layer must then be reviewed.

20.3. A client programmed for the secure web service / jSON

We have already written a client for the unsecured web service / jSON:

We will now create a client programmed for the secure web service:

  

20.3.1. The [Client HTTP] layer

 

The [Client] class handles communication with the secure web server / jSON. As we have just seen, in this HTTP communication, the client must now send an authentication header, for example:

Authorization:Basic YWRtaW46YWRtaW4=

The [IClient] interface becomes the following:


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);
}
  1. Line 8: The first parameter of the [getResponse] method is now a [Credentials] object that encapsulates a user's credentials:

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
...
}

The [Client] class, which implements the [IClient] interface, evolves as follows:


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);
        }
...
    }
...
}
  1. lines 33–35, 40–42: if the user [credentials] is not null, then the authentication header is added. The Base64 encoding of the user and their password is handled by the [getBase64] method in lines 17-21. Note that this method uses a [Base64] class belonging to JDK 1.8. Our HTTP client can work with an unsecured web service. Simply pass it a [credentials] equal to null;
  2. Apart from the preceding lines, the code remains unchanged;

20.3.2. The [DAO] layer

20.3.2.1. The [IDao] interface

  

All methods of the [IDao] interface in the [spring-webjson-client-generic] project receive an additional [Credentials credentials] parameter:


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);
 
    // particular entities - version short
    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);
 
    // particular entities - version long
    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);
}
  1. line 8: the [IDao] interface extends the following [IAuthenticate] interface:

package spring.security.client.dao;
 
import spring.security.client.entities.Credentials;
 
public interface IAuthenticate {
    // authentication
    public void authenticate(Credentials credentials);
}

The [IAuthenticate] interface has only a single method, [authenticate]. This method returns nothing (void) if the user [Credentials credentials] is accepted by the secure web service; otherwise, it throws an exception.

20.3.2.2. The [AbstractDao] class

  

Note that the [AbstractDao] class is the parent class of the [DaoCategorie] classes, which manage the URL for categories, and the [DaoProduit], which manages the URL for products. All methods of the [AbstractDao] class in the [spring-webjson-client-generic] project receive an additional parameter [Credentials credentials], which they pass to the child class. Here is an example:


    @Override
    public List<T1> getShortEntitiesById(Credentials credentials, Iterable<Long> ids) {
        // argument validity
        List<T1> entities = checkNullOrEmptyArgument(true, ids);
        if (entities != null) {
            return entities;
        }
        // result
        return getShortEntitiesById(credentials, Lists.newArrayList(ids));
}
  • The method [getShortEntitiesById] receives the parameter [Credentials credentials] (line 2), which it passes (line 9) to the method [getShortEntitiesById] of the child class;

The [AbstractDao] class has the following skeleton:


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;
 
...
}
  • line 14: the class implements the [IDao] interface that we described;
  • lines 16-17: an instance of the [IAuthenticate] interface is injected. This is implemented by the following [Authenticate] class:

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);
    }
 
}
  • line 9: the [Authenticate] class is a Spring component;
  • line 10: which implements the [IAuthenticate] interface;
  • lines 11-12: injection of the HTTP client, which enables communication with the secure web service;
  • lines 15–17: implementation of the [authenticate] method of the interface;
  • line 16: a command HTTP GET is sent to URL [/authenticate]. The use of this URL was demonstrated in Section 20.2.6. The principle is that the call terminates with an exception if the user [credentials] is either unknown or does not have sufficient permissions;

The [AbstractDao] class implements the [authenticate] method of the [IDao] interface as follows:


    @Autowired
    private IAuthenticate authenticate;
 
    @Override
    public void authenticate(Credentials credentials) {
        authenticate.authenticate(credentials);
}
  1. line 7: the task is delegated to the [authenticate] method of the [Authenticate] class. An exception will therefore occur if the user [Credentials credentials] is not accepted by the secure web service;

20.3.2.3. The [DaoCategorie, DaoProduit] classes

  

The [DaoCategorie, DaoProduit] classes are those from the [spring-webjson-server-generic] project with the additional parameter [Credentials credentials]. Here is an example:


@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. The Spring configuration

  

The [AppConfig] class configures the project's Spring environment. It is identical to what it was in the [spring-webjson-client-generic] project, with one minor difference:


@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
  • line 2: you must specify the package of the new [DAO] layer;

20.3.4. Tests for the [DAO] layer

  

20.3.4.1. The [JUnitTestCredentials] test

The [JUnitTestCredentials] test uses the [IDao.authenticate] method to verify the validity of certain users:


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);
    }
}
  1. During the initialization of the test class, lines 29–34, three users are created:
    1. The user [admin] has access to the URL of the web service. This is tested on lines 63–72;
    2. The user [user] exists but is not authorized to use the URL of the web service. It is tested on lines 37–47;
    3. The user [unknown] does not exist. This is tested in lines 50–60;

We launch the secure web service with the execution configuration named [spring-security-server-jdbc-generic] [1]:

We then run the test JUnit [JUnitTestCredentials] with the execution configuration [spring-security-client-generic-JUnitTestCredentials] [2]. The console results obtained are as follows:

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"}]

and the test passes:

  

20.3.4.2. The [JUnitTestDao] test

The [JUnitTestDao] test is identical to what it was in the unsecured project [spring-webjson-client-generic], exceptexcept that now the methods in the [DAO] layer being tested all have the user [admin / admin] as their first parameter:


@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
        ...
        // category is added - 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 };
    }
...

All operations are performed by the user [admin / admin], who is the only one authorized to access the secure web service.

We will run the test using the execution configuration named [spring-security-client-generic-JUnitTestDao]:

 

The test passes, but we can see that it is slower than with the unsecured web service. Securing an application significantly increases its response times. There is one important factor affecting the performance of the secured web service: in the [AppConfig] class that configures it, we wrote:


    @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);
        }
}

Line 17 has an impact. It determines whether or not the user is forced to authenticate on every access. If we comment it out, the duration of the JUnit test is significantly shorter, because the user [admin] authenticates only for the first test and not for subsequent ones (even if the HTTP authentication header is sent by the client, the server does not re-verify the user’s password).

20.4. The Eclipse project [spring-security-server-jpa-generic]

The secure web service will now be implemented by the [spring-security-server-jpa-generic] project, which builds on the [spring-jpa-generic] project that manages database access using Spring Data JPA:

Above:

  • The [DAO1] layer is the [DAO] layer, which manages the [PRODUITS] and [CATEGORIES] tables in the [dbproduitscategories] database. It has already been written;
  • the layer [DAO2] is the layer [DAO] that manages the tables [USERS], [ROLES], and [USERS_ROLES] from the [dbproduitscategories] database. It has yet to be written;

The [spring-security-server-jpa-generic] project is initially created by copying the previously studied [spring-security-server-jdbc-generic] project. In fact, the [web] and [security] layers do not change because:

  1. the [DAO1 / Repositories / JPA] layer (already written) has the same interface as the [DAO1 / JDBC] layer;
  2. the layer [DAO2 / Repositories / JPA] (to be written) will have the same interface as the layer [DAO2 / JDBC];

The [spring-security-server-jpa-generic] project is as follows:

  
  • The [spring.security.repositories] package implements the [repositories] layer;
  • The [spring.security.dao] package implements the [dao2] layer;

20.4.1. The Maven Project

The project is a Maven project configured by the following [pom.xml] file:


<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>
  • lines 24–27: the dependency for the [security] layer of the project;
  • lines 29-33: the dependency for the [web] layer of the project. The [spring-webjson-server-jpa-generic] project fully implements the [web] layer. This layer does not need to be written or modified;

In the end, the dependencies are as follows:

  

20.4.2. The Spring configuration

  

The [AppConfig] configuration file from the previous project, [spring-security-server-jdbc-generic], is suitable. You simply need to add an additional configuration to it:


@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 {
  • line 3: we declare the package that implements the [repositories] layer;
  • line 4: the packages containing the Spring beans have the same name in the new project;
  • line 5: in the previous project, the [spring.webjson.server.config.AppConfig] class was found in the [spring-webjson-server-jdbc-generic] dependency. Here, it will be found in the [spring-webjson-server-jpa-generic] dependency;

20.4.3. The JPA layer

The entities JPA managed by layer [JPA] are found in project [mysql-config-jpa-hibernate] [2], which is a dependency of project [1]:

The class [User] is the table image of [USERS]:

Image

  1. ID: primary key;
  2. VERSION: row versioning column;
  3. IDENTITY: a descriptive user ID;
  4. LOGIN: the user's login;
  5. PASSWORD: their 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 {
    // 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;
    }
 
    // ------------------------------------------------------------
    // redefinition of [equals] and [hashcode]
...
 
    // getters and setters
...
}
  1. line 23: the class implements the [AbstractCoreEntity] interface already used for other entities;
  2. lines 34-35: the entity type. This property is not persisted in the [@Transient] database;
  3. lines 38–43: the three basic properties of a user (name, login, password);
  4. lines 46–48: the list of the user’s roles. The user may have multiple roles. Similarly, we will see that multiple users can be associated with a single role. We therefore have, in the JPA sense of the term, a relationship [ManyToMany] between the entities [User] and [Role]:
    1. a user can be associated with multiple roles;
    2. a role can reference multiple users;

This [ManyToMany] relationship is implemented in the database by the join table [USERS_ROLES]. If a user U has a relationship with a role R, this relationship is stored in the table [USERS_ROLES] by recording the primary key pair of the entities (U,R). In the table JPA, the relationship [ManyToMany] linking the entities [User] and [Role] can be split into two relationships [ManyToOne, OneToMany]:

  • (continued)
    • a relationship [ManyToOne] from entity [User] to entity [UserRole];
    • a relationship [OneToMany] from entity [UserRole] to entity [UserRole];

Similarly, the relationship [ManyToMany] linking the entities [Role] and [User] can be split into two relationships [ManyToOne, OneToMany]:

  • (continued)
    • a relationship [ManyToOne] from entity [Role] to entity [UserRole];
    • a relationship [OneToMany] from entity [UserRole] to entity [User];
  1. relation 48: the fact that a user has multiple roles is represented by a relation [OneToMany] to the entity [UserRole];

The class [Role] is the representation of the table [ROLES]:

Image

  • ID: primary key;
  • VERSION: row versioning column;
  • NAME: role name. By default, Spring Security expects names in the form ROLE_XX, for example ROLE_ADMIN or 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
...
}
  1. lines 42–44: the fact that multiple users can be associated with a role is represented by a relationship [@OneToMany] to the entity [UserRole];

The class [UserRole] corresponds to the table [USERS_ROLES]:

Image

A user can have multiple roles, and a role can include multiple users. This is represented by a many-to-many relationship embodied by the table [USERS_ROLES].

  • ID: primary key;
  • VERSION: row versioning column;
  • USER_ID: user ID;
  • ROLE_ID: role identifier;

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
...
}
  1. lines 34–36: implement the foreign key from table [USERS_ROLES] to table [USERS];
  2. lines 38-41: implement the foreign key from table [USERS_ROLES] to table [ROLES];

20.4.4. The [repositories] layer

  

The [UserRepository] interface manages access to the [User] entities:


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> {
 
    // list of user roles identified by id
    @Query("select ur.role from UserRole ur where ur.user.id=?1")
    Iterable<Role> getRoles(long id);
 
    // list of user roles identified by a unique login
    @Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
    Iterable<Role> getRoles(String login, String password);
 
    // search for a user via login
    User findUserByLogin(String login);
}
  • line 9: the [UserRepository] interface extends the [CrudRepository] interface from Spring Data (line 7);
  • lines 12-13: the [getRoles(long id)] method retrieves all roles for a user identified by their [id]
  • lines 16-17: same as above, but for a user identified by their login and password;
  • line 20: to find a user via their login;

The [RoleRepository] interface manages access to [Role] entities:


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);
 
}
  • line 7: the [RoleRepository] interface extends the [CrudRepository] interface;
  • line 10: a role can be searched for by its name. Note that the [Role] entity has a [name] field. The method [findEntityByChamp] is automatically implemented by Spring Data. Therefore, there is no need to implement the method [finRoleByName] here. It simply needs to be declared in the interface.

The [UserRoleRepository] interface manages access to the [UserRole] entities:


package spring.security.repositories;
 
import generic.jpa.entities.dbproduitscategories.UserRole;
 
import org.springframework.data.repository.CrudRepository;
 
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
 
}
  1. line 7: the [UserRoleRepository] interface simply extends the [CrudRepository] interface without adding any new methods;

20.4.5. The [DAO2] layer

  

In the [DAO2] layer, we find the same classes as in the [DAO2] layer of the [spring-security-server-jdbc-generic] project discussed earlier in Section 20.2.2. They simply need to be implemented now using the classes from the [repositories] layer.

The [AppUserDetails] class evolves as follows:


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;
    }
...
}
  • line 31, the class constructor receives the [UserRepository] object as its second parameter, which allows the class to obtain the roles of a given user (line 42);

The Spring component [AppUserDetailsService] evolves as follows:


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);
    }
 
}
  • line 18: injection of the Spring component [userRepository], which will allow the service to return the user identified by their login, line 27;

Ultimately, we realize that we only need [userRepository] and not the other two repositories, [roleRepository, userRoleRepository]. These will be used in the next project, which aims to populate the [USERS, ROLES, USERS_ROLES] tables.

20.4.6. Testing

The secure web service is launched with the configuration named [spring-security-server-jpa-generic-hibernate-eclipselink] [1]. The generic client test [JUnitTestDao] is launched with the configuration named [spring-security-client-generic-JUnitTestDao] [2]:

The tests pass.

20.5. The Eclipse [spring-security-create-users] project

  

20.5.1. The database

Running the project populates the [USERS, ROLES, USERS_ROLES] tables from the [dbproduitscategories] table:

Image

 

The created IDs [login/passwd] are as follows: [admin/admin], [user/user], [guest/guest]. By default, passwords are encrypted.

Image

20.5.2. Maven Configuration

The project is a Maven project configured by the following [pom.xml] file:


<?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>
  • lines 22–25: dependency on the Spring Security framework. The password encryption algorithm is provided by this framework
  • lines 27–31: dependency on the [spring-security-server-jpa-generic] project that we just built. This project implements the [repositories] and [JPA] layers of the project;

In the end, the dependencies are as follows:

  

20.5.3. The [console] layer

Since the [repositories] and [JPA] layers are implemented by the [spring-security-server-jpa-generic] dependency, only the [console] layer remains to be implemented.

  
  1. [AppConfig] is the project’s Spring configuration class;
  2. [CreateUsers] is the executable class that creates users and roles;
  3. [Base64Encoder] is a support class for generating the Base64 code for a [login, password] pair. We have already used it. It is not needed for this project;

The Spring configuration class [AppConfig] is as follows:


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 {
}
  1. Line 10: Specifies where to find the [repositories] beans for the application. They are located in the [spring.security.repositories] package of the [spring-security-server-jpa-generic] dependency
  2. line 11: we import the beans from the [ConfigJpa] class, which configures the [JPA] layer of the project. This class is found in the [mysql-config-jpa-hibernate] dependency:
  

The [CreateUsers] class is as follows:


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é...");
    }
 
}
  1. lines 22–24: define the username, password, and role for three users;
  2. line 27: the Spring context is built from the [AppConfig] configuration class;
  3. lines 28-30: retrieve the references for the three [Repository] objects that may be useful for creating a user;
  4. line 31: the three users are created;
  5. lines 33–35: information to create user #i;
  6. line 37: check if the role already exists;
  7. lines 39-41: if not, we create it in the database. It will have a name of the form [ROLE_XX];
  8. line 43: check if the login already exists;
  9. lines 45–52: if the login does not exist, create it in the database;
  10. line 47: we encrypt the password. Here, we use the [BCrypt] class from Spring Security (line 8). We therefore need the archives for this framework. The [pom.xml] file includes this dependency:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. line 49: the user is persisted in the database;
  2. line 51: as well as the relationship linking them to their role;
  3. lines 55–60: if the login already exists, we check whether the role we want to assign to them is already among their roles;
  4. lines 62–64: if the role being searched for is not found, a row is created in the [USERS_ROLES] table to link the user to their role;
  5. We have not protected against potential exceptions. This is a helper class for quickly creating users;

To run the project, execute the runtime configuration named [spring-security-create-users-hibernate-eclipselink]:

 

We have just built two secure web services:

  1. one with a [security / web / JDBC / MySQL] architecture;
  2. the other with a [security / web / Hibernate / MySQL] architecture;

We will now look at two other architectures:

  • an architecture named [security / web / EclipseLink / SQL Server 2014 Express];
  • a [security / web / OpenJpa / Oracle Express] architecture;

  • In [1], we load the projects configuring a [JDBC / SQL Server] layer and a [JPA / EclipseLink / SQL Server] layer;

Note: Press Alt-F5, then regenerate all Maven projects.

We assume that the SGBD SQL Server is running and that the [dbproduitscategories] database has been generated. First, we need to populate the [USERS, ROLES, USERS_ROLES] tables in this database. To do this, run the execution configuration named [spring-security-create-users-hibernate-eclipselink]:

It should populate the three tables with data:

 
 
  • Start the secure web service using the configuration named [spring-security-server-jpa-generic-hibernate-eclipselink][1];
  • Run the test JUnitTestDao with the configuration named [spring-security-client-generic-JUnitTestDao][2]. It must succeed [3];

20.5.5. Architecture [security / web / OpenJpa / Oracle Express]

  1. In [1], we load the projects configuring a [JDBC / Oracle Express] layer and a [JPA / OpenJpa / Oracle Express] layer;

Note: Press Alt-F5, then regenerate all Maven projects.

We assume that the SGBD Oracle Express instance is running and that the [dbproduitscategories] database has been generated. First, we need to populate the [USERS, ROLES, USERS_ROLES] tables in this database. To do this, run the execution configuration named [spring-security-create-users-openjpa]:

This should populate the three tables with data:

 
 
  1. Start the secure web service with the configuration named [spring-security-server-jpa-generic-openjpa][1-2];
  2. run the test JUnitTestDao with the configuration named [spring-security-client-generic-JUnitTestDao][3]. It must succeed [4];