20. Proteger o serviço web para aceder à base de dados [dbproduitscategories]
20.1. Configurar o ambiente de desenvolvimento
Iremos implementar a segurança do serviço web utilizando os seguintes projetos:
![]() |
- os projetos [spring-security-*] podem ser encontrados na pasta [<examples>\spring-database-generic\spring-security];
- A segurança será implementada para o SGBD MySQL utilizando uma camada [DAO / JDBC], seguida de uma camada [DAO / JPA / Hibernate];
- Prima Alt-F5 e, em seguida, regenere todos os projetos Maven;
Precisamos de criar utilizadores na base de dados [dbproduitscategories]. Para tal, utilize a configuração de execução [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
A execução desta configuração preenche as tabelas [USERS, ROLES, USERS_ROLES] na base de dados [dbproduitscategories]:
![]() |
![]() |
As credenciais criadas [nome de utilizador/palavra-passe] são as seguintes: [admin/admin], [user/user], [guest/guest]. Na base de dados, as palavras-passe estão encriptadas.
![]() |

Depois de concluir este passo, execute a configuração de teste denominada [spring-security-server-jpa-generic-hibernate-eclipselink], que inicia o serviço web seguro (o MySQL deve estar em execução):
![]() | ![]() |
Em seguida, execute a configuração de teste denominada [spring-security-client-generic-JUnitTestDao], que testa o serviço web seguro:
![]() | ![]() |
O teste deve ser aprovado.
20.2. O projeto Eclipse [spring-security-server-jdbc-generic]
O serviço web seguro é implementado pelo projeto [spring-security-server-jdbc-generic]:
![]() |
Acima:
- A camada [DAO1] é a camada [DAO] que gere as tabelas [PRODUCTS] e [CATEGORIES] na base de dados [dbproduitscategories]. Já foi escrita;
- A camada [DAO2] é a camada [DAO] que gere as tabelas [USERS], [ROLES] e [USERS_ROLES] na base de dados [dbproduitscategories]. Ainda não foi escrita;
![]() |
20.2.1. A configuração do Maven
O projeto [spring-security-server-jdbc-generic] é um projeto Maven configurado pelo seguinte ficheiro [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>
- linhas 29–33: reutilizamos o código existente com o arquivo de serviço web / json / jdbc que analisámos;
- linhas 24–27: a dependência que traz as classes do Spring Security;
Em última análise, o projeto tem as seguintes dependências em relação aos outros projetos carregados no Eclipse:
![]() |
20.2.2. A camada [DAO2]
![]() |
Acima:
- A camada [DAO1] é a camada [DAO] que gere as tabelas [PRODUCTS] e [CATEGORIES] na base de dados [dbproduitscategories]. Já foi escrita;
- A camada [DAO2] é a camada [DAO] que gere as tabelas [USERS], [ROLES] e [USERS_ROLES] na base de dados [dbproduitscategories]. É esta que vamos escrever agora;
![]() |
O Spring Security requer a criação de uma classe que implemente a seguinte interface [UsersDetail]:
![]() |
Esta interface é implementada aqui pela 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"));
}
}
- linha 22: a classe [AppUserDetails] implementa a interface [UserDetails];
- linhas 29-30: a classe encapsula um utilizador (linha 19) e o repositório que fornece detalhes sobre esse utilizador (linha 20);
- linha 27: o acesso à base de dados será feito via JDBC utilizando o objeto [NamedParameterJdbcTemplate namedParameterJdbcTemplate] definido no projeto [spring-jdbc-generic-04]. Note-se que este objeto não é injetado pelo Spring, como costuma acontecer. É fornecido ao construtor nas linhas 36–39. Porquê? Porque a classe [AppUserDetails] não é um componente Spring (não possui a anotação @Component) e, portanto, não pode ser injetada;
- linhas 36–39: o construtor que instancia a classe com um utilizador e o seu repositório;
- Linhas 42–49: Implementação do método [getAuthorities] da interface [UserDetails]. Deve construir uma coleção de elementos do tipo [GrantedAuthority] ou de um tipo derivado. Aqui, usamos o tipo derivado [SimpleGrantedAuthority] (linha 46), que encapsula o nome de uma das funções do utilizador da linha 29;
- linhas 45–47: percorremos a lista de funções do utilizador da linha 29 para construir uma lista de elementos do tipo [SimpleGrantedAuthority];
- linha 45: para obter as funções do utilizador, utilizamos o método privado [getRoles] da linha 53;
- linha 56: executa a seguinte instrução SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] (definida em [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";
Esta consulta SQL realiza uma junção entre as três tabelas [USERS, ROLES, USERS_ROLES] para recuperar as funções de um utilizador identificado pela sua chave primária. É parametrizada pela chave primária [:id] do utilizador cujas funções estão a ser consultadas.
- linha 56: cada linha de resultados do [SELECT] é convertida numa entidade [Role] pela classe [ShortRowMapper] nas linhas 66–72;
Voltemos ao código da 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
...
}
- linhas 35–37: implementa o método [getPassword] da interface [UserDetails]. Devolvemos a palavra-passe do utilizador da linha 12;
- linhas 39–42: implementar o método [getUserName] da interface [UserDetails]. Devolvemos o nome de utilizador do utilizador da linha 12;
- linhas 44–47: a conta do utilizador nunca expira;
- linhas 49–52: a conta do utilizador nunca é bloqueada;
- linhas 54–57: as credenciais do utilizador nunca expiram;
- linhas 59–62: a conta do utilizador está sempre ativa;
O Spring Security também requer a existência de uma classe que implemente a interface [AppUserDetailsService]:
![]() |
Esta interface é implementada pela seguinte 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"));
}
}
- linha 21: a classe será um componente Spring;
- linhas 25-26: o acesso à base de dados será feito via JDBC utilizando o objeto [NamedParameterJdbcTemplate] definido nos beans do projeto [spring-jdbc-generic-04];
- linhas 31-49: implementação do método [loadUserByUsername] da interface [UserDetailsService] (linha 22). O parâmetro é o nome de utilizador do utilizador;
- linhas 36–37: o utilizador é pesquisado pelo seu nome de utilizador. A instrução SQL [ConfigJdbc.SELECT_USER_BYLOGIN] é a seguinte:
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";
Cada linha devolvida pela instrução SELECT é convertida numa entidade [User] pela classe [ShortUserMapper] nas linhas 52–58.
- Linhas 42–44: Se não for encontrada, é lançada uma exceção;
- linha 46: um objeto [AppUserDetails] é construído e devolvido. É, de facto, do tipo [UserDetails] (linha 32). Duas informações são passadas ao seu construtor:
- o utilizador que foi encontrado;
- o objeto [namedParameterJdbcTemplate] que permitirá à classe [AppUserDetails] consultar a base de dados;
20.2.3. A camada [web]
![]() |
O projeto [spring-security-server-jdbc-generic] depende do projeto [spring-webjson-server-jdbc-generic]:
![]() |
Este projeto implementa a camada [web]. Não é necessário modificá-lo.
20.2.4. Configuração de segurança do projeto
![]() |
O projeto é configurado pela seguinte classe [AppConfig]:
1 ![]() |
Já nos deparámos com uma classe de configuração do Spring Security (ver Secção 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");
}
}
Seguiremos o mesmo procedimento:
- linha 11: definir uma classe que estenda a classe [WebSecurityConfigurerAdapter];
- linha 13: definir um método [configure(HttpSecurity http)] que define os direitos de acesso às várias URLs do serviço web;
- linha 19: definir um método [configure(AuthenticationManagerBuilder auth)] que define os utilizadores e as suas funções;
A classe [AppConfig] será a seguinte:
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);
}
}
}
- linha 17: a classe é uma classe de configuração do Spring;
- linha 18: ativa os componentes do Spring Security;
- linha 26: recuperamos os componentes Spring da camada [DAO2] e os do pacote [spring.security.service], que discutiremos mais adiante;
- linha 23: importa os beans do projeto [spring-webjson-server-jdbc-generic], que implementa a camada [web]. Entre esses beans estão também os da camada [DAO1];
- linhas 22–23: a classe [AppUserDetails], que fornece acesso aos utilizadores da aplicação, é injetada;
- linha 26: um booleano que protege (true) ou não protege (false) a aplicação web;
- linhas 28–33: o método [configure(HttpSecurity http)] define os utilizadores e as suas funções. Recebe um [AuthenticationManagerBuilder] como parâmetro. Este parâmetro é enriquecido com duas informações (linha 32):
- uma referência ao [appUserDetailsService] da linha 23, que fornece acesso aos utilizadores registados. Note-se aqui que o facto de estarem armazenados numa base de dados não é explicitamente mencionado. Podem, portanto, estar num cache, fornecidos por um serviço web, etc.
- o tipo de encriptação utilizado para a palavra-passe. Utilizámos o algoritmo BCrypt;
- linhas 35–53: o método [configure(HttpSecurity http)] define os direitos de acesso às URLs do serviço web;
- linha 38: vimos no projeto introdutório que, por predefinição, o Spring Security gere um token CSRF (Cross-Site Request Forgery) que o utilizador que deseja autenticar-se deve enviar de volta ao servidor. Aqui, este mecanismo está desativado. Isto, combinado com o booleano (isSecured=false), permite que a aplicação web seja utilizada sem segurança;
- linha 42: ativamos a autenticação através de cabeçalhos HTTP. O cliente deve enviar o seguinte cabeçalho HTTP:
onde code é a codificação Base64 da cadeia de caracteres login:password. Por exemplo, a codificação Base64 da cadeia de caracteres admin:admin é YWRtaW46YWRtaW4=. Portanto, um utilizador com o nome de utilizador [admin] e a palavra-passe [admin] enviará o seguinte cabeçalho HTTP para se autenticar:
- Linhas 47–49: indicam que todas as URLs do serviço web são acessíveis a utilizadores com a função [ROLE_ADMIN]. Isto significa que um utilizador sem esta função não pode aceder ao serviço web;
- Linha 51: No modo [session], um utilizador que se tenha autenticado uma vez não precisa de o fazer para acessos subsequentes. Esta é a configuração padrão do Spring Security. A linha 51 desativa este modo. Se ativado, o utilizador deve autenticar-se em cada acesso. Sem uma sessão, o serviço web seguro é menos responsivo do que com uma sessão, pelo que a linha 51 foi comentada;
20.2.5. Testar o serviço web seguro
Iremos testar o serviço web utilizando o cliente Chrome [Advanced Rest Client]. Teremos de especificar o cabeçalho de autenticação HTTP:
onde [código] é a cadeia codificada em Base64 [login:password]. Para gerar este código, pode utilizar o seguinte programa do projeto [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 executarmos este programa com os dois argumentos [admin admin]:
![]() |
obtemos o seguinte resultado:
Estamos agora prontos para o teste:
- o SGBD MySQL deve estar em execução;
- preenchemos as tabelas [PRODUCTS] e [CATEGORIES] utilizando a configuração de execução denominada [spring-jdbc-generic-04-fillDataBase]:
![]() |
- Se isso ainda não tiver sido feito, preenchemos as tabelas [USERS, ROLES, USERS_ROLES] utilizando a configuração de execução denominada [spring-security-create-users-hibernate-eclipselink]:
![]() |
- Iniciamos o serviço web seguro utilizando a configuração de tempo de execução denominada [spring-security-server-jdbc-generic]:
![]() |
Em seguida, utilizando o cliente Chrome [Advanced Rest Client], solicitamos a versão longa de todas as categorias:
![]() |
- Em [1], solicitamos a URL das descrições completas das categorias;
- em [2], utilizando o método GET;
- em [3], fornecemos o cabeçalho HTTP de autenticação. O código [YWRtaW46YWRtaW4=] é a codificação Base64 da cadeia [admin:admin];
- em [4], enviamos o pedido HTTP;
A resposta do servidor é a seguinte:
![]() |
- em [1], o cabeçalho de autenticação HTTP;
- em [2], o servidor devolve uma resposta JSON;
Conseguimos obter a lista de categorias:
![]() |
Agora vamos tentar uma solicitação HTTP com um cabeçalho de autenticação incorreto. A resposta é então a seguinte:
![]() |
- em [1]: o cabeçalho de autenticação HTTP;
Recebemos a seguinte resposta:
![]() |
- em [2]: a resposta do serviço web;
Agora, vamos experimentar o utilizador / utilizador. Ele existe, mas não tem acesso ao serviço web. Se executarmos o programa de codificação Base64 com os dois argumentos [utilizador utilizador]:
![]() |
obtemos o seguinte resultado:
![]() |
- em [1]: o cabeçalho de autenticação HTTP incorreto;
![]() |
- em [2]: a resposta do serviço web. Diferencia-se da anterior, que era [401 Não autorizado]. Desta vez, o utilizador autenticou-se corretamente, mas não possui permissões suficientes para aceder ao URL;
Um serviço web seguro está agora operacional.
20.2.6. Um URL de autenticação
![]() |
Vamos criar um URL que nos permita determinar se um utilizador está autorizado a aceder ao serviço web. Para tal, criamos o seguinte novo controlador 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);
}
}
- linha 9: a classe [AuthenticateController] é um controlador Spring. Como tal, expõe URLs. A anotação [@RestController] indica que os métodos que tratam destas URLs devolvem as suas próprias respostas ao cliente;
- linha 11: expõe a URL [/authenticate];
- linhas 12–14: o método simplesmente devolve um objeto [Response] vazio, mas com um [status] igual a 0, indicando que não ocorreu nenhum erro;
Para que serve esta URL? Quando quisermos simplesmente autenticar um utilizador, iremos solicitá-la. Vimos que, se a camada de segurança não aceitar este utilizador, lança uma exceção. Aqui está um exemplo;
Com o utilizador [admin:admin]:
![]() | ![]() |
Recebemos uma resposta vazia, mas sem exceção.
Com o utilizador [user:user]:
![]() | ![]() |
Ocorreu uma exceção.
20.2.7. Conclusão
As classes necessárias para o Spring Security foram adicionadas sem alterar o projeto web/JSON original. Este cenário ideal deve-se ao facto de as três tabelas adicionadas à base de dados serem independentes das tabelas existentes. Poderíamos até tê-las colocado numa base de dados separada. Noutros casos, as tabelas adicionadas podem ter relações com tabelas existentes. O código na camada [DAO] existente deve, então, ser revisto.
20.3. Um cliente programado para o serviço web/JSON seguro
Já escrevemos um cliente para o serviço web/JSON não seguro:
![]() |
Vamos agora criar um cliente programado para o serviço web seguro:
![]() |
![]() |
20.3.1. A camada [Cliente HTTP]
![]() |
![]() | ![]() |
A classe [Client] gere a comunicação HTTP com o servidor web seguro / JSON. Como acabámos de ver, nesta comunicação HTTP, o cliente deve agora enviar um cabeçalho de autenticação, por exemplo:
A interface [IClient] passa a ser a seguinte:
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);
}
- Linha 8: O primeiro parâmetro do método [getResponse] é agora um objeto [Credentials] que encapsula as credenciais do utilizador:
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
...
}
A classe [Client], que implementa a interface [IClient], evolui da seguinte forma:
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);
}
...
}
...
}
- linhas 33–35, 40–42: se as [credenciais] do utilizador não forem nulas, então o cabeçalho de autenticação é adicionado. A codificação Base64 do nome de utilizador e da palavra-passe é tratada pelo método [getBase64] nas linhas 17–21. Note-se que este método utiliza uma classe [Base64] do JDK 1.8. O nosso cliente HTTP pode funcionar com um serviço web não seguro. Basta passar-lhe um [credentials] igual a nulo;
- À exceção das linhas anteriores, o código permanece inalterado;
20.3.2. A camada [DAO]
![]() |
20.3.2.1. A interface [IDao]
![]() |
Todos os métodos da interface [IDao] no projeto [spring-webjson-client-generic] recebem um parâmetro [Credentials] adicional:
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);
}
- Linha 8: A interface [IDao] estende a seguinte interface [IAuthenticate]:
package spring.security.client.dao;
import spring.security.client.entities.Credentials;
public interface IAuthenticate {
// authentication
public void authenticate(Credentials credentials);
}
A interface [IAuthenticate] possui apenas um único método, [authenticate]. Este método não retorna nada (void) se o utilizador [Credentials credentials] for aceite pelo serviço web seguro; caso contrário, lança uma exceção.
20.3.2.2. A classe [AbstractDao]
![]() |
Recorde-se que a classe [AbstractDao] é a classe pai das classes [DaoCategorie], que gerem URLs de categorias, e da classe [DaoProduit], que gere URLs de produtos. Todos os métodos da classe [AbstractDao] no projeto [spring-webjson-client-generic] recebem um parâmetro adicional [Credentials credentials] que passam para a classe filha. Aqui está um exemplo:
@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));
}
- O método [getShortEntitiesById] recebe o parâmetro [Credentials] (linha 2), que passa (linha 9) para o método [getShortEntitiesById] da classe filha;
A classe [AbstractDao] tem a seguinte estrutura:
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;
...
}
- linha 14: a classe implementa a interface [IDao] que descrevemos;
- linhas 16–17: é injetada uma instância da interface [IAuthenticate]. Esta é implementada pela seguinte 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);
}
}
- linha 9: a classe [Authenticate] é um componente Spring;
- linha 10: que implementa a interface [IAuthenticate];
- linhas 11–12: injeção do cliente HTTP que permite a comunicação com o serviço web seguro;
- linhas 15–17: implementação do método [authenticate] da interface;
- linha 16: é enviada uma solicitação HTTP GET para a URL [/authenticate]. A utilização desta URL foi demonstrada na secção 20.2.6. O princípio é que a chamada resulta numa exceção se as [credenciais] do utilizador forem desconhecidas ou não tiverem permissões suficientes;
A classe [AbstractDao] implementa o método [authenticate] da interface [IDao] da seguinte forma:
@Autowired
private IAuthenticate authenticate;
@Override
public void authenticate(Credentials credentials) {
authenticate.authenticate(credentials);
}
- Linha 7: A tarefa é delegada ao método [authenticate] da classe [Authenticate]. Será, portanto, lançada uma exceção se o utilizador [Credentials credentials] não for aceite pelo serviço web seguro;
20.3.2.3. As classes [DaoCategorie, DaoProduit]
![]() |
As classes [DaoCategorie, DaoProduit] são as do projeto [spring-webjson-server-generic] com o parâmetro adicional [Credentials credentials]. Aqui está um exemplo:
@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. A configuração do Spring
![]() |
A classe [AppConfig] configura o ambiente Spring do projeto. É idêntica à do projeto [spring-webjson-client-generic], com uma exceção:
@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
- linha 2: deve especificar o pacote da nova camada [DAO];
20.3.4. Testes para a camada [DAO]
![]() |
![]() |
20.3.4.1. O teste [JUnitTestCredentials]
O teste [JUnitTestCredentials] utiliza o método [IDao.authenticate] para verificar a validade de determinados utilizadores:
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 a inicialização da classe de teste, nas linhas 29–34, são criados três utilizadores:
- o utilizador [admin] tem acesso aos URLs do serviço web. Isto é testado nas linhas 63–72;
- o utilizador [user] existe, mas não está autorizado a utilizar os URLs do serviço web. São testados nas linhas 37–47;
- o utilizador [unknown] não existe. É testado nas linhas 50–60;
Lançamos o serviço web seguro com a configuração de tempo de execução denominada [spring-security-server-jdbc-generic] [1]:
![]() |
Em seguida, executamos o teste JUnit [JUnitTestCredentials] com a configuração de tempo de execução [spring-security-client-generic-JUnitTestCredentials] [2]. Os resultados obtidos na consola são os seguintes:
e o teste é aprovado:
![]() |
20.3.4.2. O teste [JUnitTestDao]
O teste [JUnitTestDao] é idêntico ao que era no projeto [spring-webjson-client-generic] não seguro, exceto que agora os métodos da camada [DAO] que estão a ser testados têm todos o utilizador [admin / admin] como primeiro parâmetro:
@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 };
}
...
Todas as operações são realizadas utilizando o utilizador [admin / admin], que é o único com direitos de acesso ao serviço web seguro.
Iremos executar o teste utilizando a configuração de execução denominada [spring-security-client-generic-JUnitTestDao]:
![]() |
O teste é bem-sucedido, mas podemos ver que é mais lento do que com o serviço web não seguro. Proteger uma aplicação aumenta significativamente os seus tempos de resposta. Há um fator importante que afeta o desempenho do serviço web protegido: na classe [AppConfig] que o configura, escrevemos:
@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);
}
}
A linha 17 tem impacto. Determina se o utilizador é ou não obrigado a autenticar-se em cada acesso. Se a comentarmos, a duração do teste JUnit é significativamente mais curta, porque o utilizador [admin] apenas se autentica no primeiro teste e não nos subsequentes (mesmo que o cabeçalho de autenticação HTTP seja enviado pelo cliente, o servidor não volta a verificar a palavra-passe do utilizador).
20.4. O projeto Eclipse [spring-security-server-jpa-generic]
O serviço web seguro será agora implementado pelo projeto [spring-security-server-jpa-generic], que se baseia no projeto [spring-jpa-generic] que gere o acesso à base de dados utilizando o Spring Data JPA:
![]() |
Acima:
- a camada [DAO1] é a camada [DAO] que gere as tabelas [PRODUCTS] e [CATEGORIES] na base de dados [dbproduitscategories]. Já foi escrita;
- a camada [DAO2] é a camada [DAO] que gere as tabelas [USERS], [ROLES] e [USERS_ROLES] na base de dados [dbproduitscategories]. Ainda não foi escrita;
O projeto [spring-security-server-jpa-generic] é inicialmente criado através da clonagem do projeto anteriormente estudado [spring-security-server-jdbc-generic]. Na verdade, as camadas [web] e [security] permanecem inalteradas porque:
- A camada [DAO1 / Repositories / JPA] (já escrita) tem a mesma interface que a camada [DAO1 / JDBC];
- a camada [DAO2 / Repositories / JPA] (a ser escrita) terá a mesma interface que a camada [DAO2 / JDBC];
O projeto [spring-security-server-jpa-generic] é o seguinte:
![]() |
- o pacote [spring.security.repositories] implementa a camada [repositories];
- o pacote [spring.security.dao] implementa a camada [dao2];
20.4.1. O projeto Maven
O projeto é um projeto Maven configurado pelo seguinte ficheiro [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>
- linhas 24–27: a dependência para a camada [security] do projeto;
- linhas 29–33: a dependência para a camada [web] do projeto. O projeto [spring-webjson-server-jpa-generic] implementa totalmente a camada [web]. Esta camada não precisa de ser escrita nem modificada;
No final, as dependências são as seguintes:
![]() |
20.4.2. A configuração do Spring
![]() |
O ficheiro de configuração [AppConfig] do projeto anterior [spring-security-server-jdbc-generic] é adequado. Basta adicionar uma configuração adicional:
@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 {
- linha 3: declaramos o pacote que implementa a camada [repositories];
- linha 4: os pacotes que contêm os beans Spring têm o mesmo nome no novo projeto;
- linha 5: no projeto anterior, a classe [spring.webjson.server.config.AppConfig] encontrava-se na dependência [spring-webjson-server-jdbc-generic]. Aqui, ela encontra-se na dependência [spring-webjson-server-jpa-generic];
20.4.3. A camada JPA
![]() |
As entidades JPA geridas pela camada [JPA] encontram-se no projeto [mysql-config-jpa-hibernate] [2], que é uma dependência do projeto [1]:
![]() |
A classe [User] corresponde à tabela [USERS]:

- ID: chave primária;
- VERSION: coluna de versionamento de linhas;
- IDENTITY: um identificador descritivo para o utilizador;
- LOGIN: o nome de utilizador do utilizador;
- PASSWORD: a palavra-passe do utilizador;
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
...
}
- linha 23: a classe implementa a interface [AbstractCoreEntity] já utilizada para as outras entidades;
- linhas 34–35: o tipo de entidade. Esta propriedade não é persistida na base de dados [@Transient];
- linhas 38–43: as três propriedades básicas de um utilizador (nome, login, palavra-passe);
- linhas 46–48: a lista de funções do utilizador. Um utilizador pode ter várias funções. Da mesma forma, veremos que uma função pode estar associada a vários utilizadores. Assim, no sentido do termo JPA, existe uma relação [ManyToMany] entre as entidades [User] e [Role]:
- Um utilizador pode ser atribuído a várias funções;
- Uma função pode estar associada a vários utilizadores;
Esta relação [ManyToMany] é implementada na base de dados através da tabela de ligação [USERS_ROLES]. Se um utilizador U tiver uma relação com uma função R, essa relação é armazenada na tabela [USERS_ROLES] através do registo do par de chaves primárias das entidades (U,R). No lado do JPA, a relação [ManyToMany] que liga as entidades [User] e [Role] pode ser dividida em duas relações [ManyToOne, OneToMany]:
- (continuação)
- uma relação [ManyToOne] da entidade [User] para a entidade [UserRole];
- uma relação [OneToMany] da entidade [UserRole] para a entidade [UserRole];
Da mesma forma, a relação [ManyToMany] que liga as entidades [Role] e [User] pode ser dividida em duas relações [ManyToOne, OneToMany]:
- (continuação)
- uma relação [ManyToOne] da entidade [Role] para a entidade [UserRole];
- uma relação [OneToMany] da entidade [UserRole] para a entidade [User];
- linha 48: o facto de um utilizador ter várias funções é representado por uma relação [OneToMany] com a entidade [UserRole];
A classe [Role] representa a tabela [ROLES]:

- ID: chave primária;
- VERSION: coluna de versionamento de linhas;
- NAME: nome da função. Por predefinição, o Spring Security espera nomes no formato ROLE_XX, por exemplo, ROLE_ADMIN ou 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
...
}
- linhas 42–44: o facto de vários utilizadores poderem estar associados a uma função é representado por uma relação [@OneToMany] com a entidade [UserRole];
A classe [UserRole] representa a tabela [USERS_ROLES]:

Um utilizador pode ter várias funções e uma função pode ter vários utilizadores. Temos uma relação muitos-para-muitos representada pela tabela [USERS_ROLES].
- ID: chave primária;
- VERSION: coluna de versionamento de linhas;
- USER_ID: identificador do utilizador;
- ROLE_ID: identificador de uma função;
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
...
}
- linhas 34–36: implementar a chave estrangeira da tabela [USERS_ROLES] para a tabela [USERS];
- linhas 38–41: implementar a chave estrangeira da tabela [USERS_ROLES] para a tabela [ROLES];
20.4.4. A camada [repositories]
![]() |
![]() |
A interface [UserRepository] gere o acesso às entidades [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);
}
- linha 9: a interface [UserRepository] estende a interface [CrudRepository] do Spring Data (linha 7);
- linhas 12-13: o método [getRoles(long id)] recupera todas as funções de um utilizador identificado pelo seu [id]
- linhas 16-17: igual ao anterior, mas para um utilizador identificado pelo seu nome de utilizador e palavra-passe;
- Linha 20: para encontrar um utilizador pelo seu nome de utilizador;
A interface [RoleRepository] gere o acesso às entidades [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);
}
- linha 7: a interface [RoleRepository] estende a interface [CrudRepository];
- linha 10: É possível pesquisar uma função pelo seu nome. Note que a entidade [Role] possui um campo [name]. O método [findEntityByField] é implementado automaticamente pelo Spring Data. Portanto, não há necessidade de implementar o método [findRoleByName] aqui. Basta declará-lo na interface.
A interface [UserRoleRepository] gere o acesso às entidades [UserRole]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.UserRole;
import org.springframework.data.repository.CrudRepository;
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
}
- Linha 7: A interface [UserRoleRepository] simplesmente estende a interface [CrudRepository] sem adicionar nenhum método novo;
20.4.5. A camada [DAO2]
![]() |
![]() |
A camada [DAO2] contém as mesmas classes que a camada [DAO2] no projeto [spring-security-server-jdbc-generic] discutido anteriormente na Secção 20.2.2. Agora, basta implementá-las utilizando as classes da camada [repositories].
A classe [AppUserDetails] evolui da seguinte forma:
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;
}
...
}
- Na linha 31, o construtor da classe recebe o objeto [UserRepository] como segundo parâmetro, o que permite à classe recuperar as funções de um determinado utilizador (linha 42);
O componente Spring [AppUserDetailsService] evolui da seguinte forma:
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);
}
}
- linha 18: injeção do componente Spring [userRepository], que permitirá ao serviço devolver o utilizador identificado pelo seu login, linha 27;
Por fim, percebemos que precisamos apenas do [userRepository] e não dos outros dois repositórios [roleRepository, userRoleRepository]. Estes serão utilizados no próximo projeto, que visa preencher as tabelas [USERS, ROLES, USERS_ROLES].
20.4.6. Testes
O serviço web seguro é iniciado com a configuração denominada [spring-security-server-jpa-generic-hibernate-eclipselink] [1]. O teste [JUnitTestDao] para o cliente genérico é iniciado com a configuração denominada [spring-security-client-generic-JUnitTestDao] [2]:
![]() |
Os testes são bem-sucedidos.
20.5. O projeto Eclipse [spring-security-create-users]
![]() |
![]() |
20.5.1. A base de dados
A execução do projeto preenche as tabelas [USERS, ROLES, USERS_ROLES] na base de dados [dbproduitscategories]:

![]() |
![]() |
As credenciais criadas [nome de utilizador/palavra-passe] são as seguintes: [admin/admin], [user/user], [guest/guest]. Por predefinição, as palavras-passe são encriptadas.
![]() |

20.5.2. Configuração do Maven
O projeto é um projeto Maven configurado pelo seguinte ficheiro [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>
- linhas 22–25: dependência da estrutura Spring Security. O algoritmo de encriptação de senhas é fornecido por esta estrutura
- linhas 27–31: dependência do projeto [spring-security-server-jpa-generic] que acabámos de criar. Este projeto implementa as camadas [repositories] e [JPA] do projeto;
No final, as dependências são as seguintes:
![]() |
20.5.3. A camada [da consola]
![]() |
Uma vez que as camadas [repositórios] e [JPA] são implementadas pela dependência [spring-security-server-jpa-generic], resta apenas implementar a camada [console].
![]() |
- [AppConfig] é a classe de configuração Spring do projeto;
- [CreateUsers] é a classe executável que cria utilizadores e funções;
- [Base64Encoder] é uma classe auxiliar para gerar o código Base64 para um par [login, palavra-passe]. Já a utilizámos. Não é necessária para este projeto;
A classe de configuração do Spring [AppConfig] é a seguinte:
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 {
}
- Linha 10: Especifica onde encontrar os [repositórios] da aplicação. Estes estão localizados no pacote [spring.security.repositories] da dependência [spring-security-server-jpa-generic]
- Linha 11: Importa os beans da classe [ConfigJpa], que configura a camada [JPA] do projeto. Esta classe encontra-se na dependência [mysql-config-jpa-hibernate]:
![]() |
A classe [CreateUsers] é a seguinte:
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é...");
}
}
- linhas 22–24: definem o nome de utilizador, a palavra-passe e a função para três utilizadores;
- linha 27: o contexto Spring é construído a partir da classe de configuração [AppConfig];
- linhas 28–30: recuperam as referências aos três objetos [Repository] que podem ser usados para criar um utilizador;
- linha 31: cria os três utilizadores;
- linhas 33–35: informações para criar o utilizador #i;
- linha 37: verificamos se a função já existe;
- linhas 39–41: caso contrário, criamo-la na base de dados. Terá um nome no formato [ROLE_XX];
- linha 43: verificamos se o login já existe;
- linhas 45–52: se o nome de utilizador não existir, criá-lo na base de dados;
- linha 47: encriptamos a palavra-passe. Aqui, usamos a classe [BCrypt] do Spring Security (linha 8). Por isso, precisamos dos arquivos para esta estrutura. O ficheiro [pom.xml] inclui esta dependência:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- linha 49: o utilizador é guardado na base de dados;
- linha 51: assim como a relação que o liga à sua função;
- linhas 55–60: se o login já existir, verificamos se a função que pretendemos atribuir-lhe já se encontra entre as suas funções;
- linhas 62–64: se a função procurada não for encontrada, é criada uma linha na tabela [USERS_ROLES] para ligar o utilizador à sua função;
- Não nos protegemos contra possíveis exceções. Esta é uma classe auxiliar para criar utilizadores rapidamente;
Para executar o projeto, execute a configuração de execução denominada [spring-security-create-users-hibernate-eclipselink]:
![]() |
Acabámos de criar dois serviços web seguros:
- um com uma arquitetura [segurança / web / JDBC / MySQL];
- o outro com uma arquitetura [segurança / web / Hibernate / MySQL];
Vamos agora analisar duas outras arquiteturas:
- uma arquitetura [segurança / web / EclipseLink / SQL Server 2014 Express];
- uma arquitetura [segurança / web / OpenJpa / Oracle Express];
20.5.4. uma arquitetura [segurança / web / EclipseLink / SQL Server]
![]() |
![]() |
- Em [1], carregamos os projetos configurando uma camada [JDBC / SQL Server] e uma camada [JPA / EclipseLink / SQL Server];
Nota: Prima Alt-F5 e, em seguida, regenere todos os projetos Maven.
Partimos do princípio de que o SGBD SQL Server está em execução e que a base de dados [dbproduitscategories] foi gerada. Primeiro, precisamos de preencher as tabelas [USERS, ROLES, USERS_ROLES] nesta base de dados. Para tal, execute a configuração de execução denominada [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
Isto deverá preencher as três tabelas com dados:
![]() | ![]() |
![]() |
![]() |
- Inicie o serviço web seguro utilizando a configuração denominada [spring-security-server-jpa-generic-hibernate-eclipselink][1];
- execute o teste JUnitTestDao com a configuração denominada [spring-security-client-generic-JUnitTestDao][2]. Deve ser aprovado [3];
![]() |
![]() |
20.5.5. Arquitetura [segurança / web / OpenJpa / Oracle Express]
![]() |
![]() |
- Em [1], carregue os projetos que configuram uma camada [JDBC / Oracle Express] e uma camada [JPA / OpenJpa / Oracle Express];
Nota: Prima Alt-F5 e, em seguida, regenere todos os projetos Maven.
Partimos do princípio de que o SGBD Oracle Express está em execução e que a base de dados [dbproduitscategories] foi gerada. Primeiro, precisamos de preencher as tabelas [USERS, ROLES, USERS_ROLES] nesta base de dados. Para tal, execute a configuração de execução denominada [spring-security-create-users-openjpa]:
![]() | ![]() |
Isto deverá preencher as três tabelas com dados:
![]() | ![]() |
![]() |
![]() |
- Inicie o serviço web seguro utilizando a configuração denominada [spring-security-server-jpa-generic-openjpa][1-2];
- execute o teste JUnitTestDao com a configuração denominada [spring-security-client-generic-JUnitTestDao][3]. Deve ser aprovado [4];
![]() |
![]() |



























































































