20. Proteção do serviço web de acesso ao banco de dados [dbproduitscategories]
20.1. Configuração do ambiente de trabalho
Vamos implementar a segurança do serviço web com os seguintes projetos:
![]() |
- os projetos [spring-security-*] estarão na pasta [<exemples>\spring-database-generic\spring-security];
- a segurança será implementada para os projetos SGBD e MySQL com uma camada [DAO / JDBC] e, em seguida, uma camada [DAO / JPA / Hibernate];
- pressione Alt-F5 e, em seguida, gere novamente todos os projetos Maven;
Precisamos criar usuários no banco de dados [dbproduitscategories]. Para isso, use a configuração de execução [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
A execução dessa configuração preenche as tabelas [USERS, ROLES, USERS_ROLES] a partir da tabela [dbproduitscategories]:
![]() |
![]() |
Os identificadores criados [login/passwd] são os seguintes: [admin/admin], [user/user], [guest/guest]. Por padrão, as senhas são criptografadas.
![]() |

Feito isso, execute a configuração de execução 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 execução denominada [spring-security-client-generic-JUnitTestDao], que testa o serviço web seguro:
![]() | ![]() |
O teste deve ser bem-sucedido.
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 gerencia as tabelas [PRODUITS] e [CATEGORIES] do banco de dados [dbproduitscategories]. Ela já foi criada;
- a camada [DAO2] é a camada [DAO] que gerencia as tabelas [USERS], [ROLES] e [USERS_ROLES] do banco de dados [dbproduitscategories]. Ela ainda precisa ser criada;
![]() |
20.2.1. A configuração do Maven
O projeto [spring-security-server-jdbc-generic] é um projeto Maven configurado pelo seguinte arquivo [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>
<!-- servidor web / jSON -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- plug-ins -->
<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: retoma-se o que já existe com o arquivo do serviço web / json / jdbc analisado;
- linhas 24-27: a dependência que traz as classes do Spring Security;
No final, o projeto apresenta 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 gerencia as tabelas [PRODUITS] e [CATEGORIES] do banco de dados [dbproduitscategories]. Ela já foi criada;
- a camada [DAO2] é a camada [DAO] que gerencia as tabelas [USERS], [ROLES] e [USERS_ROLES] do banco de dados [dbproduitscategories]. É essa que vamos criar agora;
![]() |
O Spring Security exige a criação de uma classe que implemente a seguinte interface [UsersDetail]:
![]() |
Essa 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;
// propriedades
private User user;
private String simpleClassName = getClass().getSimpleName();
// construtores
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étodos privados----------------
private List<Role> getRoles(Long id) {
try {
// buscamos o usuário pelo seu 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 usuário (linha 19) e o repositório que permite obter os detalhes desse usuário (linha 20);
- linha 27: o acesso ao banco de dados será feito por meio de JDBC, utilizando o objeto [NamedParameterJdbcTemplate namedParameterJdbcTemplate] definido no projeto [spring-jdbc-generic-04]. Observe-se que esse objeto não é injetado pelo Spring, como costumava ser feito. Ele é fornecido ao construtor nas linhas 36-39. Por quê? Porque a classe [AppUserDetails] não é um componente do Spring (ausência da anotação @Component) e, portanto, não é possível realizar injeções nela;
- linhas 36-39: o construtor que instancia a classe com um usuário e seu repositório;
- linhas 42-49: implementação do método [getAuthorities] da interface [UserDetails]. Ele deve construir uma coleção de elementos do tipo [GrantedAuthority] ou derivado. Aqui, utilizamos o tipo derivado [SimpleGrantedAuthority] (linha 46), que encapsula o nome de uma das funções do usuário da linha 29;
- linhas 45-47: percorremos a lista de funções do usuário da linha 29 para construir uma lista de elementos do tipo [SimpleGrantedAuthority];
- linha 45: para obter as funções do usuário, utiliza-se o método privado [getRoles] da linha 53;
- linha 56: executa a ordem SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] (definida em [Configjdbc]) da seguinte forma:
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";
Essa consulta SQL realiza uma junção entre as três tabelas [USERS, ROLES, USERS_ROLES] para obter as funções de um usuário identificado por sua chave primária. Ela é configurada pela chave primária [:id] do usuário cujas funções estão sendo pesquisadas.
- linha 56: cada linha de resultado da consulta [SELECT] é transformada em 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;
// propriedades
private User user;
private String simpleClassName = getClass().getSimpleName();
// construtores
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 e setters
...
}
- linhas 35-37: implementam o método [getPassword] da interface [UserDetails]. Retorna-se a senha do usuário da linha 12;
- linhas 39-42: implementam o método [getUserName] da interface [UserDetails]. Retorna-se o login do usuário da linha 12;
- linhas 44-47: a conta do usuário nunca expira;
- linhas 49-52: a conta do usuário nunca é bloqueada;
- linhas 54-57: as credenciais do usuário nunca expiram;
- linhas 59-62: a conta do usuário está sempre ativa;
O Spring Security também exige a existência de uma classe que implemente a interface [AppUserDetailsService]:
![]() |
Essa 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 {
// injeções
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// local
private String simpleClassName = getClass().getSimpleName();
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
List<User> users;
try {
// procurando o usuário pelo login
users = namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_USER_BYLOGIN,
Collections.singletonMap("login", login), new ShortUserMapper());
} catch (Exception e) {
throw new DaoException(145, e, simpleClassName);
}
// encontrado?
if (users.size() == 0) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// exibindo os detalhes do usuário
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 ao banco de dados será feito por meio de JDBC, utilizando o objeto [NamedParameterJdbcTemplate 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 login do usuário;
- linhas 36-37: o usuário é pesquisado por meio de seu login. A ordem 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 retornada pelo SELECT é transformada na entidade [User] pela classe [ShortUserMapper] das linhas 52-58.
- linhas 42-44: se não for encontrado, é lançada uma exceção;
- linha 46: um objeto [AppUserDetails] é criado e renderizado. Ele é, de fato, do tipo [UserDetails] (linha 32). Passamos duas informações para seu construtor:
- o usuário que foi encontrado;
- o objeto [namedParameterJdbcTemplate], que permitirá que a classe [AppUserDetails] faça uma consulta ao banco de dados;
20.2.3. A camada [web]
![]() |
O projeto [spring-security-server-jdbc-generic] depende do projeto [spring-webjson-server-jdbc-generic]:
![]() |
É esse projeto que implementa a camada [web]. Ela não precisa ser modificada.
20.2.4. A configuração de segurança do projeto
![]() |
O projeto é configurado pela seguinte classe [AppConfig]:
1 ![]() |
Já nos deparamos com uma classe de configuração do Spring Security (ver parágrafo 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 diferentes URL do serviço web;
- linha 19: definir um método [configure(AuthenticationManagerBuilder auth)] que define os usuários e 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;
// segurança
private boolean activateSecurity = true;
@Override
protected void configure(AuthenticationManagerBuilder registry) throws Exception {
// a autenticação é feita pelo bean [appUserDetailsService]
// a senha é criptografada pelo algoritmo de hash BCrypt
registry.userDetailsService(appUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// aplicativo seguro?
if (activateSecurity) {
// a senha é transmitida pelo cabeçalho Authorization: Basic xxxx
http.httpBasic();
// o método HTTP OPTIONS deve ser autorizado para todos
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// apenas a função ADMIN pode utilizar o aplicativo
http.authorizeRequests() //
.antMatchers("/", "/**") // todas as URL
.hasRole("ADMIN");
// com ou sem sessão?
//http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
}
- linha 17: a classe é uma classe de configuração do Spring;
- linha 18: ativa os elementos do Spring Security;
- linha 26: recuperamos os componentes Spring da camada [DAO2] e os do pacote [spring.security.service], sobre os quais falaremos posteriormente;
- linha 23: importam-se 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 dá acesso aos usuários do aplicativo, é injetada;
- linha 26: um valor booleano que protege (true) ou não (false) o aplicativo web;
- linhas 28-33: o método [configure(HttpSecurity http)] define os usuários e suas funções. Ele recebe como parâmetro um tipo [AuthenticationManagerBuilder]. Esse parâmetro é complementado com duas informações (linha 32):
- uma referência ao serviço [appUserDetailsService] da linha 23, que dá acesso aos usuários registrados. Observe-se aqui que o fato de eles estarem registrados em um banco de dados não é mencionado. Portanto, eles poderiam estar em um cache, fornecidos por um serviço web, etc.
- o tipo de criptografia utilizado para a senha. Utilizamos o algoritmo BCrypt;
- linhas 35-53: o método [configure(HttpSecurity http)] define os direitos de acesso aos URL do serviço web;
- linha 38: vimos no projeto de introdução que, por padrão, o Spring Security gerencia um token CSRF (Cross Site Request Forgery) que o usuário que deseja se autenticar deve enviar de volta ao servidor. Aqui, esse mecanismo está desativado. Isso, somado ao valor booleano (isSecured=false), permite utilizar o aplicativo web sem segurança;
- linha 42: ativamos o modo de autenticação por cabeçalho HTTP. O cliente deverá enviar o seguinte cabeçalho HTTP:
onde “code” é a codificação da sequência “login:password” pelo algoritmo Base64. Por exemplo, a codificação Base64 da sequência admin:admin é YWRtaW46YWRtaW4=. Portanto, o usuário com login [admin] e senha [admin] enviará o cabeçalho HTTP a seguir para se autenticar:
- linhas 47-49: indicam que todos os URL do serviço web estão acessíveis aos usuários com a função [ROLE_ADMIN]. Isso significa que um usuário que não possua essa função não pode acessar o serviço web;
- linha 51: no modo [session], um usuário que já se autenticou uma vez não precisa se autenticar novamente para acessos subsequentes. Esse é o valor padrão do Spring Security. A linha 51 desativa esse modo. Se estiver ativa, o usuário precisará se autenticar a cada acesso. Sem sessão, a capacidade de resposta do serviço web seguro é menor do que com a sessão; por isso, a linha 51 foi colocada entre comentários;
20.2.5. Testes do serviço web seguro
Vamos testar o serviço web com o cliente Chrome [Advanced Rest Client]. Precisaremos especificar o cabeçalho de autenticação HTTP:
onde [code] é o código Base64 da sequência [login:password]. Para gerar esse código, pode-se usar 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) {
// são esperados dois argumentos: login e senha
if (args.length != 2) {
System.out.println("Syntaxe : login password");
System.exit(0);
}
// recupera-se os dois argumentos
String chaîne = String.format("%s:%s", args[0], args[1]);
// codifica a string
byte[] data = Base64.encode(chaîne.getBytes());
// exibe sua codificação Base64
System.out.println(new String(data));
}
}
Se executarmos esse programa com os dois argumentos [admin admin]:
![]() |
obtemos o seguinte resultado:
Agora estamos prontos para os testes:
- o SGBD MySQL deve ser executado;
- preenchemos as tabelas [PRODUITS] e [CATEGORIES] com a configuração de execução denominada [spring-jdbc-generic-04-fillDataBase]:
![]() |
- se ainda não tiver sido feito, preenchemos as tabelas [USERS, ROLES, USERS_ROLES] com a configuração de execução denominada [spring-security-create-users-hibernate-eclipselink]:
![]() |
- Iniciamos o serviço web seguro com a configuração de execução denominada [spring-security-server-jdbc-generic]:
![]() |
Em seguida, com o cliente Chrome [Advanced Rest Client], solicitamos a versão completa de todas as categorias:
![]() |
- em [1], solicitamos o URL das categorias completas;
- em [2], com o método GET;
- em [3], fornecemos o cabeçalho HTTP da autenticação. O código [YWRtaW46YWRtaW4=] é a codificação Base64 da sequência [admin:admin];
- em [4], enviamos o comando HTTP;
A resposta do servidor é a seguinte:
![]() |
- em [1], o cabeçalho de autenticação HTTP;
- em [2], o servidor retorna uma resposta jSON;
Conseguimos obter a lista de categorias:
![]() |
Vamos agora 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;
Obtemos a seguinte resposta:
![]() |
- em [2]: a resposta do serviço web;
Agora, vamos tentar com o usuário user / user. Ele existe, mas não tem acesso ao serviço web. Se executarmos o programa de codificação Base64 com os dois argumentos [user user]:
![]() |
obtemos o seguinte resultado:
![]() |
- em [1]: o cabeçalho de autenticação HTTP está incorreto;
![]() |
- em [2]: a resposta do serviço web. Ela é diferente da anterior, que era [401 Unauthorized]. Desta vez, o usuário se autenticou corretamente, mas não possui permissões suficientes para acessar o URL;
Um serviço web seguro está agora operacional.
20.2.6. Um URL de autenticação
![]() |
Vamos criar um URL que nos permitirá saber se um usuário está autorizado ou não a acessar o serviço web. Para isso, criamos o novo controlador MVC [AuthenticateController] a seguir:
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. Nessa função, ela expõe URL. A anotação [@RestController] indica que os métodos que processam esses URL enviam eles próprios a resposta ao cliente;
- linha 11: exibe o URL [/authenticate];
- linhas 12-14: o método se limita a retornar um objeto [Response] vazio, mas com um [status] igual a 0, indicando que não houve erro;
Para que serve esse URL? Quando quisermos simplesmente autenticar um usuário, vamos solicitá-lo. Vimos que, se a camada de segurança não aceitar esse usuário, ela retorna uma exceção. Aqui está um exemplo;
Com o usuário [admin:admin]:
![]() | ![]() |
Recebemos uma resposta vazia, mas sem exceção.
Com o usuário [user:user]:
![]() | ![]() |
Ocorreu uma exceção.
20.2.7. Conclusão
A adição das classes necessárias ao Spring Security pôde ser feita sem alterações no projeto web/json original. Esse cenário muito favorável decorre do fato de que as três tabelas adicionadas ao banco de dados são independentes das tabelas existentes. Elas poderiam até mesmo ter sido colocadas em um banco de dados separado. Em outros casos, as tabelas adicionadas podem ter relações com as tabelas existentes. O código da camada [DAO] existente deve, então, ser revisado.
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:
![]() |
Agora, vamos criar um cliente programado para o serviço web seguro:
![]() |
![]() |
20.3.1. A camada [Client HTTP]
![]() |
![]() | ![]() |
A classe [Client] garante a comunicação HTTP com o servidor web / jSON seguro. Como acabamos de ver, nessa 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 os identificadores de um usuário:
package spring.security.client.entities;
public class Credentials {
// propriedades
private String login;
private String password;
// construtor
public Credentials() {
}
public Credentials(String login, String password) {
this.login = login;
this.password = password;
}
// getters e setters
...
}
A classe [Client], que implementa a interface [IClient], sofreu as seguintes alterações:
package spring.security.client.dao;
...
@Component
public class Client implements IClient {
// injeções
@Autowired
protected RestTemplate restTemplate;
@Autowired
protected String urlServiceWebJson;
// local
private String simpleClassName = getClass().getSimpleName();
private String getBase64(Credentials credentials) {
// codifica o usuário e sua senha em base 64 — requer 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())));
}
// solicitação genérica
@Override
public <T1, T2> T1 getResponse(Credentials credentials, String url, HttpMethod method, int errStatus, T2 body) {
// a resposta do servidor
ResponseEntity<Response<T1>> response;
try {
// prepara-se a solicitação
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);
}
// a solicitação é executada
response = restTemplate.exchange(request, new ParameterizedTypeReference<Response<T1>>() {
});
} catch (Exception e) {
// encapsulando a exceção
throw new DaoException(errStatus, e, simpleClassName);
}
...
}
...
}
- linhas 33-35, 40-42: se o usuário [credentials] não for nulo, então é adicionado o cabeçalho de autenticação. A codificação Base64 do usuário e de sua senha é realizada pelo método [getBase64] nas linhas 17-21. É importante observar que esse método utiliza uma classe [Base64] pertencente ao JDK 1.8. Nosso cliente HTTP pode funcionar com um serviço web não seguro. Basta passar a ele um [credentials] igual a null;
- fora 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] do projeto [spring-webjson-client-generic] recebem um parâmetro adicional [Credentials credentials]:
package spring.security.client.dao;
import java.util.List;
import spring.security.client.entities.AbstractCoreEntity;
import spring.security.client.entities.Credentials;
public interface IDao<T extends AbstractCoreEntity> extends IAuthenticate {
// lista de todas as entidades T
public List<T> getAllShortEntities(Credentials credentials);
public List<T> getAllLongEntities(Credentials credentials);
// de entidades específicas — versão resumida
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);
// de entidades específicas — versão longa
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);
// atualização de várias entidades
public List<T> saveEntities(Credentials credentials, Iterable<T> entities);
public List<T> saveEntities(Credentials credentials, @SuppressWarnings("unchecked") T... entities);
// exclusão de todas as entidades
public void deleteAllEntities(Credentials credentials);
// remoção de várias entidades
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 {
// autenticação
public void authenticate(Credentials credentials);
}
A interface [IAuthenticate] possui apenas o método [authenticate]. Esse método não retorna nada (void) se o usuário [Credentials credentials] for aceito pelo serviço web seguro; caso contrário, lança uma exceção.
20.3.2.2. A classe [AbstractDao]
![]() |
Vale lembrar que a classe [AbstractDao] é a classe pai das classes [DaoCategorie], que gerenciam os URL das categorias, e da classe [DaoProduit], que gerencia os URL dos produtos. Todos os métodos da classe [AbstractDao] do projeto [spring-webjson-client-generic] recebem um parâmetro adicional [Credentials credentials], que são repassados à classe filha. Veja um exemplo:
@Override
public List<T1> getShortEntitiesById(Credentials credentials, Iterable<Long> ids) {
// validade do argumento
List<T1> entities = checkNullOrEmptyArgument(true, ids);
if (entities != null) {
return entities;
}
// resultado
return getShortEntitiesById(credentials, Lists.newArrayList(ids));
}
- o método [getShortEntitiesById] recebe o parâmetro [Credentials credentials] (linha 2), que ele transmite (linha 9) ao 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: uma instância da interface [IAuthenticate] é injetada. 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;
// verificação [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: um comando HTTP GET é enviado para o URL [/authenticate]. O uso desse URL foi demonstrado no parágrafo 20.2.6. Seu princípio é que a chamada termina com uma exceção se o usuário [credentials] for desconhecido ou não tiver permissões suficientes;
A classe [AbstractDao] implementa o método [authenticate] da interface [IDao] da seguinte maneira:
@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]. Portanto, ocorrerá uma exceção se o usuário [Credentials credentials] não for aceito pelo serviço web seguro;
20.3.2.3. As turmas [DaoCategorie, DaoProduit]
![]() |
As classes [DaoCategorie, DaoProduit] são as do projeto [spring-webjson-server-generic] com o parâmetro adicional [Credentials credentials]. Veja um exemplo:
@Component
public class DaoCategorie extends AbstractDao<Categorie> {
// injeções
@Autowired
protected ApplicationContext context;
@Autowired
protected IClient client;
@Override
public List<Categorie> getAllShortEntities(Credentials credentials) {
try {
// filtros jSON
ObjectMapper mapper = context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// obter todas as categorias
Object map = client.<List<Categorie>, Void> getResponse(credentials, "/getAllShortCategories", HttpMethod.GET,
202, null);
// a lista de categorias List<Categoria>
return mapper.readValue(mapper.writeValueAsString(map), new TypeReference<List<Categorie>>() {
});
} catch (DaoException e1) {
throw e1;
} catch (Exception e2) {
throw new DaoException(221, e2, simpleClassName);
}
}
....
20.3.3. A configuração do Spring
![]() |
A classe [AppConfig] configura o ambiente Spring do projeto. Ela é idêntica à do projeto [spring-webjson-client-generic], com uma única diferença:
@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
- linha 2: é preciso inserir o pacote da nova camada [DAO];
20.3.4. Testes da camada [DAO]
![]() |
![]() |
20.3.4.1. O teste [JUnitTestCredentials]
O teste [JUnitTestCredentials] utiliza o método [IDao.authenticate] para verificar a validade ou não de determinados usuários:
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 {
// camada [DAO]
@Autowired
private IAuthenticate authenticate;
// usuários
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 a 34, são criados três usuários:
- o usuário [admin] tem acesso aos URL do serviço web. Isso é testado nas linhas 63 a 72;
- o usuário [user] existe, mas não está autorizado a utilizar os URL do serviço web. Ele é testado nas linhas 37 a 47;
- o usuário [unknown] não existe. Isso é testado nas linhas 50 a 60;
Iniciamos o serviço web seguro com a configuração de execução denominada [spring-security-server-jdbc-generic] [1]:
![]() |
Em seguida, inicia-se o teste JUnit [JUnitTestCredentials] com a configuração de execução [spring-security-client-generic-JUnitTestCredentials] [2]. Os resultados obtidos no console são os seguintes:
e o teste foi bem-sucedido:
![]() |
20.3.4.2. O teste [JUnitTestDao]
O teste [JUnitTestDao] é idêntico ao que era no projeto não seguro [spring-webjson-client-generic], excetoque agora os métodos da camada [DAO] testados têm todos como primeiro parâmetro o usuário [admin / admin]:
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestDao {
// contexto Spring
@Autowired
private ApplicationContext context;
// camada [DAO]
@Autowired
private IDao<Produit> daoProduit;
@Autowired
private IDao<Categorie> daoCategorie;
....
// usuários
static private Credentials admin;
@BeforeClass
public static void init() {
admin = new Credentials("admin", "admin");
}
@Before
public void clean() {
// limpa-se o banco de dados antes de cada teste
log("Vidage de la base de données", 1);
// esvazia-se a tabela [CATEGORIES] e, em cadeia, a tabela [PRODUITS]
daoCategorie.deleteAllEntities(admin);
// esvazia-se os dicionários
for (Long id : mapCategories.keySet()) {
mapCategories.remove(id);
}
for (Long id : mapProduits.keySet()) {
mapProduits.remove(id);
}
}
private List<Categorie> fill(int nbCategories, int nbProduits) {
// preenche-se as tabelas
...
// adição da categoria — em cascata, os produtos também serão
// inseridos — o resultado é exibido simultaneamente
return daoCategorie.saveEntities(admin, categories);
}
private Object[] showDataBase() throws BeansException, JsonProcessingException {
// lista de categorias
log("Liste des catégories", 2);
List<Categorie> categories = daoCategorie.getAllShortEntities(admin);
affiche(categories, context.getBean("jsonMapperShortCategorie", ObjectMapper.class));
// lista de produtos
log("Liste des produits", 2);
List<Produit> produits = daoProduit.getAllShortEntities(admin);
affiche(produits, context.getBean("jsonMapperShortProduit", ObjectMapper.class));
// resultado
return new Object[] { categories, produits };
}
...
Todas as operações são realizadas com o usuário [admin / admin], que é o único com direito de acesso ao serviço web seguro.
Iniciaremos o teste com a configuração de execução denominada [spring-security-client-generic-JUnitTestDao]:
![]() |
O teste é aprovado, mas percebe-se que ele é mais lento do que com o serviço web não seguro. A segurança de uma aplicação aumenta significativamente seus tempos de resposta. É possível observar um fator importante no desempenho do serviço web seguro: na classe [AppConfig], que o configura, escrevemos:
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// aplicativo seguro?
if (activateSecurity) {
// a senha é transmitida pelo cabeçalho Authorization: Basic xxxx
http.httpBasic();
// o método HTTP OPTIONS deve ser autorizado para todos
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// apenas a função ADMIN pode utilizar o aplicativo
http.authorizeRequests() //
.antMatchers("/", "/**") // todas as URL
.hasRole("ADMIN");
// sem sessão
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
A linha 17 tem um custo. Ela determina se o usuário é obrigado ou não a se autenticar a cada acesso. Se a colocarmos entre comentários, a duração do teste JUnit é significativamente menor, isso porque o usuário [admin] só se autentica no primeiro teste e não nos seguintes (mesmo que o cabeçalho de autenticação HTTP seja enviado pelo cliente, o servidor não verifica novamente a senha do usuário).
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], responsável pelo gerenciamento do acesso ao banco de dados com o Spring Data JPA:
![]() |
Acima:
- a camada [DAO1] é a camada [DAO], que gerencia as tabelas [PRODUITS] e [CATEGORIES] do banco de dados [dbproduitscategories]. Ela já foi criada;
- a camada [DAO2] é a camada [DAO] que gerencia as tabelas [USERS], [ROLES] e [USERS_ROLES] do banco de dados [dbproduitscategories]. Ela ainda precisa ser criada;
O projeto [spring-security-server-jpa-generic] é obtido inicialmente por meio da cópia do projeto analisado anteriormente, [spring-security-server-jdbc-generic]. De fato, as camadas [web] e [security] não sofrem alterações, pois:
- a camada [DAO1 / Repositories / JPA] (já escrita) possui a mesma interface que a camada [DAO1 / JDBC];
- a camada [DAO2 / Repositories / JPA] (a ser gravada) 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 arquivo [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>
<!-- servidor web / jSON -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jpa-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- plug-ins -->
<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 da camada [security] do projeto;
- linhas 29-33: a dependência da camada [web] do projeto. O projeto [spring-webjson-server-jpa-generic] implementa totalmente a camada [web]. Esta não precisa ser escrita nem modificada;
No final, as dependências são as seguintes:
![]() |
20.4.2. A configuração do Spring
![]() |
O arquivo de configuração [AppConfig] do projeto anterior [spring-security-server-jdbc-generic] é adequado. Basta adicionar uma configuração adicional a ele:
@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: declara-se o pacote que implementa a camada [repositories];
- linha 4: os pacotes que contêm os beans do Spring têm o mesmo nome no novo projeto;
- linha 5: no projeto anterior, a classe [spring.webjson.server.config.AppConfig] era encontrada na dependência [spring-webjson-server-jdbc-generic]. Aqui, ela será encontrada na dependência [spring-webjson-server-jpa-generic];
20.4.3. A camada JPA
![]() |
As entidades JPA gerenciadas pela camada [JPA] estão no projeto [mysql-config-jpa-hibernate] [2], que é uma dependência do projeto [1]:
![]() |
A classe [User] é a imagem da tabela [USERS]:

- ID: chave primária;
- VERSION: coluna de controle de versão da linha;
- IDENTITY: uma identidade descritiva do usuário;
- LOGIN: o login do usuário;
- PASSWORD: sua senha;
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 {
// propriedades
@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;
// propriedades
@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;
// os UserRole associados
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
@JsonIgnore
private List<UserRole> userRoles;
// fabricantes
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;
}
// ------------------------------------------------------------
// redefinição de [equals] e [hashcode]
...
// getters e setters
...
}
- linha 23: a classe implementa a interface [AbstractCoreEntity] já utilizada para as outras entidades;
- linhas 34-35: o tipo da entidade. Essa propriedade não é persistida no banco de dados [@Transient];
- linhas 38-43: as três propriedades básicas de um usuário (nome, login, senha);
- linhas 46-48: a lista de funções do usuário. Ele pode ter várias. Da mesma forma, veremos que a uma função podem estar associados vários usuários. Temos, portanto, no sentido do termo, uma relação entre as entidades:
- um usuário pode estar vinculado a várias funções;
- uma função pode estar associada a vários usuários;
Essa relação [ManyToMany] é implementada no banco de dados pela tabela de junção [USERS_ROLES]. Se um usuário U tiver uma relação com uma função R, essa relação é inserida na tabela [USERS_ROLES], registrando-se o par de chaves primárias das entidades (U, R). Na tabela JPA, a relação [ManyToMany], que vincula 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];
- ligação 48: o fato de um usuário ter várias funções é representado por uma relação [OneToMany] com a entidade [UserRole];
A classe [Role] é a representação da tabela [ROLES]:

- ID: chave primária;
- VERSION: coluna de controle de versão da linha;
- NAME: nome da função. Por padrã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 {
// propriedades
@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;
// propriedades
@Column(name = ConfigJdbc.TAB_ROLES_NAME, length = 30, unique = true, nullable = false)
private String name;
// os UserRole associados
@OneToMany(fetch = FetchType.LAZY, mappedBy = "role", cascade = { CascadeType.ALL })
@JsonIgnore
private List<UserRole> userRoles;
// construtores
public Role() {
}
public Role(Long id, Long version, String name) {
this.id = id;
this.version = version;
this.name = name;
}
// getters e setters
public Role(String name) {
this.name = name;
}
// ------------------------------------------------------------
// redefinição de [equals] e [hashcode]
...
// getters e setters
...
}
- linhas 42-44: o fato de que uma função pode estar associada a vários usuários é representado por uma relação [@OneToMany] com a entidade [UserRole];
A classe [UserRole] é a representação da tabela [USERS_ROLES]:

Um usuário pode ter várias funções, e uma função pode abranger vários usuários. Temos uma relação muitos-para-muitos representada pela tabela [USERS_ROLES].
- ID: chave primária;
- VERSION: coluna de controle de versão da linha;
- USER_ID: identificador de um usuário;
- 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 {
// propriedades
@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;
// um UserRole faz referência a um User
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_USER_ID, nullable = false)
private User user;
// um UserRole faz referência a um Role
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_ROLE_ID, nullable = false)
private Role role;
// construtores
public UserRole() {
}
public UserRole(User user, Role role) {
this.user = user;
this.role = role;
}
// ------------------------------------------------------------
// redefinição de [equals] e [hashcode]
...
// getters e setters
...
}
- linhas 34-36: representam a chave estrangeira da tabela [USERS_ROLES] para a tabela [USERS];
- linhas 38-41: definem a chave estrangeira da tabela [USERS_ROLES] para a tabela [ROLES];
20.4.4. A camada [repositories]
![]() |
![]() |
A interface [UserRepository] gerencia 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> {
// lista de funções de um usuário identificado por seu ID
@Query("select ur.role from UserRole ur where ur.user.id=?1")
Iterable<Role> getRoles(long id);
// lista de funções de um usuário identificado por seu login exclusivo
@Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
Iterable<Role> getRoles(String login, String password);
// busca de um usuário por meio de seu 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)] permite obter todas as funções de um usuário identificado por seu [id]
- linhas 16-17: o mesmo, mas para um usuário identificado por seu login/senha;
- linha 20: para localizar um usuário por meio de seu login;
A interface [RoleRepository] gerencia os acessos à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> {
// busca de uma função pelo nome
Role findRoleByName(String name);
}
- linha 7: a interface [RoleRepository] estende a interface [CrudRepository];
- linha 10: é possível pesquisar uma função pelo nome. Vale lembrar que a entidade [Role] possui um campo [name]. O método [findEntityByChamp] é implementado automaticamente pelo Spring Data. Portanto, não há necessidade de implementar o método [finRoleByName] aqui. Basta declará-lo na interface.
A interface [UserRoleRepository] gerencia 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] limita-se a estender a interface [CrudRepository] sem adicionar novos métodos a ela;
20.4.5. A camada [DAO2]
![]() |
![]() |
Na camada [DAO2], encontramos as mesmas classes que na camada [DAO2] do projeto [spring-security-server-jdbc-generic] analisado anteriormente no parágrafo 20.2.2. Basta implementá-las agora com a ajuda das classes da camada [repositories].
A classe [AppUserDetails] sofre as seguintes alterações:
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;
// propriedades
private User user;
private UserRepository userRepository;
// local
private String simpleClassName = getClass().getSimpleName();
// construtores
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 como segundo parâmetro o objeto [UserRepository], que permite que a classe obtenha as funções de um determinado usuário (linha 42);
O componente Spring [AppUserDetailsService], por sua vez, é alterado 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 {
// procurando o usuário pelo login
User user;
try {
user = userRepository.findUserByLogin(login);
} catch (Exception e) {
throw new DaoException(168, e, simpleClassName);
}
// encontrado?
if (user == null) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// exibindo os detalhes do usuário
return new AppUserDetails(user, userRepository);
}
}
- linha 18: injeção do componente Spring [userRepository], que permitirá que o serviço identifique o usuário por meio de seu login, linha 27;
No final, 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. Os testes
O serviço web seguro é iniciado com a configuração denominada [spring-security-server-jpa-generic-hibernate-eclipselink] [1]. O teste [JUnitTestDao] do 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. O banco de dados
A execução do projeto preenche as tabelas [USERS, ROLES, USERS_ROLES] a partir da tabela [dbproduitscategories]:

![]() |
![]() |
Os identificadores criados [login/passwd] são os seguintes: [admin/admin], [user/user], [guest/guest]. No banco de dados, as senhas estão criptografadas.
![]() |

20.5.2. Configuração do Maven
O projeto é um projeto Maven configurado pelo seguinte arquivo [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 do framework Spring Security. O algoritmo de criptografia de senhas é fornecido por esse framework
- linhas 27-31: dependência do projeto [spring-security-server-jpa-generic] que acabamos de construir. Esse projeto implementa as camadas [repositories] e [JPA] do projeto;
No final, as dependências são as seguintes:
![]() |
20.5.3. A camada [console]
![]() |
Como as camadas [repositories] 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 usuários e funções;
- [Base64Encoder] é uma classe de suporte para gerar o código Base64 de um par [login, password]. Já a utilizamos. Ela não é útil 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: indica-se onde encontrar os [repositories] do aplicativo. Eles estão no pacote [spring.security.repositories] da dependência [spring-security-server-jpa-generic]
- linha 11: importam-se os beans da classe [ConfigJpa], que configura a camada [JPA] do projeto. Essa classe será encontrada 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) {
// fim
System.out.println("Travail en cours...");
// criando três usuários
String[] logins = { "admin", "user", "guest" };
String[] passwds = { "admin", "user", "guest" };
String[] roles = { "admin", "user", "guest" };
// Contexto Spring
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserRepository userRepository = context.getBean(UserRepository.class);
RoleRepository roleRepository = context.getBean(RoleRepository.class);
UserRoleRepository userRoleRepository = context.getBean(UserRoleRepository.class);
for (int i = 0; i < logins.length; i++) {
// recuperando as informações do usuário nº i
String login = logins[i];
String password = passwds[i];
String roleName = String.format("ROLE_%s", roles[i].toUpperCase());
// a função já existe?
Role role = roleRepository.findRoleByName(roleName);
// se não existir, ele é criado
if (role == null) {
role = roleRepository.save(new Role(roleName));
}
// o usuário já existe?
User user = userRepository.findUserByLogin(login);
// se não existir, ele é criado
if (user == null) {
// a senha é hashada com bcrypt
String crypt = BCrypt.hashpw(password, BCrypt.gensalt());
// salvamos o usuário
user = userRepository.save(new User(null, null, login, login, crypt));
// criamos a relação com a função
userRoleRepository.save(new UserRole(user, role));
} else {
// o usuário já existe — ele possui a função solicitada?
boolean trouvé = false;
for (Role r : userRepository.getRoles(user.getId())) {
if (r.getName().equals(roleName)) {
trouvé = true;
break;
}
}
// se não for encontrado, cria-se a relação com a função
if (!trouvé) {
userRoleRepository.save(new UserRole(user, role));
}
}
}
// fechamento do contexto Spring
context.close();
// fim
System.out.println("Travail terminé...");
}
}
- linhas 22-24: definem o login, a senha e a função de três usuários;
- linha 27: o contexto Spring é construído a partir da classe de configuração [AppConfig];
- linhas 28-30: recuperam-se as referências das três instâncias de [Repository] que podem ser úteis para criar um usuário;
- linha 31: criam-se os três usuários;
- linhas 33-35: as informações para criar o usuário nº i;
- linha 37: verifica-se se a função já existe;
- linhas 39-41: se não for o caso, criamos a função no banco de dados. Ela terá um nome do tipo [ROLE_XX];
- linha 43: verifica-se se o login já existe;
- linhas 45-52: se o login não existir, ele é criado no banco de dados;
- linha 47: criptografamos a senha. Aqui, utilizamos a classe [BCrypt] do Spring Security (linha 8). Portanto, precisamos dos arquivos desse framework. O arquivo [pom.xml] inclui essa dependência:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- linha 49: o usuário é armazenado no banco de dados;
- linha 51: assim como a relação que o vincula à sua função;
- linhas 55-60: caso o login já exista – verifica-se, então, se entre suas funções já se encontra a função que se deseja atribuir a ele;
- linhas 62-64: se a função procurada não for encontrada, cria-se uma linha na tabela [USERS_ROLES] para vincular o usuário à sua função;
- não há proteção contra possíveis exceções. Trata-se de uma classe de suporte para criar usuários rapidamente;
Para executar o projeto, será executada a configuração de execução denominada [spring-security-create-users-hibernate-eclipselink]:
![]() |
Acabamos de criar dois serviços web seguros:
- um com a arquitetura [security / web / JDBC / MySQL];
- o outro com a arquitetura [security / web / Hibernate / MySQL];
Abordaremos agora duas outras arquiteturas:
- uma arquitetura [security / web / EclipseLink / SQL Server 2014 Express];
- uma arquitetura [security / web / OpenJpa / Oracle Express];
20.5.4. Arquitetura [security / web / EclipseLink / SQL Server]
![]() |
![]() |
- na [1], carregam-se os projetos que configuram uma camada [JDBC / SQL Server] e uma camada [JPA / EclipseLink / SQL Server];
Observação: pressione Alt-F5 e, em seguida, gere novamente todos os projetos Maven.
Supõe-se que o servidor SGBD SQL esteja em execução e que o banco de dados [dbproduitscategories] tenha sido gerado. Primeiro, precisamos preencher as tabelas [USERS, ROLES, USERS_ROLES] desse banco de dados. Para isso, execute a configuração de execução denominada [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
Ela deve preencher as três tabelas com dados:
![]() | ![]() |
![]() |
![]() |
- inicie o serviço web seguro com 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]. Ele deve ser bem-sucedido [3];
![]() |
![]() |
20.5.5. Arquitetura [security / web / OpenJpa / Oracle Express]
![]() |
![]() |
- no [1], carregam-se os projetos que configuram uma camada [JDBC / Oracle Express] e uma camada [JPA / OpenJpa / Oracle Express];
Observação: pressione Alt-F5 e, em seguida, gere novamente todos os projetos Maven.
Supõe-se que o Oracle Express SGBD esteja em execução e que o banco de dados [dbproduitscategories] tenha sido gerado. Primeiro, precisamos preencher as tabelas [USERS, ROLES, USERS_ROLES] desse banco de dados. Para isso, execute a configuração de execução denominada [spring-security-create-users-openjpa]:
![]() | ![]() |
Ela deve preencher as três tabelas com dados:
![]() | ![]() |
![]() |
![]() |
- inicie o serviço web seguro com 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]. O teste deve ser bem-sucedido [4];
![]() |
![]() |



























































































