Skip to main content

Um guia de validação de entrada com Spring Boot

Escrito por
Headshot of Lucien Chemaly

Lucien Chemaly

feature bean validation

12 de setembro de 2023

0 minutos de leitura
{"id":1,"name":"John Doe Updated","email":"john.doe.updated@example.com","password":"newP@ssW0rd!"}

Se você é desenvolvedor Java, provavelmente conhece o Spring Boot, um framework robusto que simplifica o desenvolvimento de aplicações independentes, baseadas em Spring e prontas para produção. Entre os muitos recursos que ele oferece está o Bean Validation, essencial para garantir a integridade dos dados e melhorar a experiência do usuário.

O Bean Validation pode ser usado em aplicações Spring Boot para validar entradas e formulários, validar dados antes de salvá-los em um banco de dados e aplicar diferentes políticas de segurança. Com ele, desenvolvedores podem evitar erros, melhorar a qualidade geral da aplicação e garantir a consistência dos dados em toda a aplicação.

Neste artigo, você vai conhecer as diferentes aplicações do Bean Validation e aprender a implementá-lo no Spring Boot para usá-lo com eficiência nos seus próprios projetos.

O que é Bean Validation

O Bean Validation permite aplicar restrições aos modelos de dados da sua aplicação para garantir que os dados sigam regras específicas antes de serem processados ou armazenados. Isso é possível por meio da integração com o Jakarta Bean Validation, que fornece um conjunto de anotações e interfaces para definir e validar restrições.

Com o Bean Validation, os desenvolvedores economizam tempo e recursos ao detectar erros e inconsistências logo no início do processo de desenvolvimento. Além disso, ele ajuda a melhorar a experiência geral do usuário ao reduzir a quantidade de dados incorretos inseridos.

Veja alguns casos de uso comuns do Bean Validation no desenvolvimento de aplicações:

Validação de entradas e formulários

Um dos casos de uso mais comuns do Bean Validation é a validação de entradas e formulários. Isso ajuda a garantir que os usuários informem os dados no formato correto (por exemplo, validação de um formulário de cadastro com endereços de e-mail, nomes de usuário e senhas).

Validação de dados antes de salvá-los em um banco de dados

Outra aplicação do Bean Validation é validar os dados antes de salvá-los em um banco de dados. Essa técnica ajuda a garantir que os dados informados sejam consistentes e precisos. Assim, é possível evitar erros e reduzir o risco de corrupção dos dados.

Aplicação de políticas de segurança

O Bean Validation também pode ser usado para aplicar políticas de segurança, validando entradas de usuários ou dados mapeados do banco de dados para garantir que atendam a critérios específicos, como os requisitos de força de uma senha. A validação tem um papel importante na prevenção de ataques de injeção e ajuda a evitar vulnerabilidades de segurança, como cross-site scripting (XSS) armazenado.

Validação da lógica de negócios

O Bean Validation pode ser usado para validar a lógica de negócios e garantir que ela atenda a requisitos ou padrões específicos. Ao definir restrições de validação, como a complexidade da senha ou quantidades e preços válidos de itens de um pedido, o Bean Validation verifica os dados automaticamente e aplica as regras necessárias. Isso elimina a necessidade de verificações manuais e garante que a aplicação siga padrões ou requisitos específicos.

Implementação do Bean Validation no Spring Boot

Neste tutorial, você vai implementar o Bean Validation em uma aplicação simples de criação, leitura, atualização e exclusão (CRUD) com um banco de dados em memória. Os usuários informam nome, e-mail e senha, que precisam atender a critérios específicos para serem aceitos. A arquitetura de alto nível usada aqui inclui um servidor de back-end com APIs RESTful e um banco de dados H2 em memória.

Quando uma aplicação cliente chama o serviço web (a aplicação deste tutorial), a solicitação é enviada ao UserController. Em seguida, o controller valida os dados da solicitação. Se os dados forem válidos, a solicitação segue para o UserRepository, que se comunica com o banco de dados em memória e retorna a resposta correspondente. Caso contrário, os dados são inválidos e a solicitação retorna ao cliente com uma mensagem de erro:

Fluxograma que mostra uma solicitação do cliente passando pela validação de beans no UserController até o UserRepository, com caminhos para casos válidos e erros de validação.

Pré-requisitos

Antes de começar a implementação, verifique se você instalou as seguintes ferramentas e tecnologias:

Crie um novo projeto Spring Boot

Para criar um novo projeto Spring Boot, acesse o Spring Initializr e escolha as seguintes opções:

  • Tipo de projeto: Maven Project

  • Linguagem: Java

  • Empacotamento: Jar

  • Versão do Java: 17

  • Spring Boot: 3.1.0 (SNAPSHOT)

Informe os seguintes dados na seção Metadados do projeto:

  • Group: com.example

  • Artifact: simple-crud-bean-validation

  • Name: simple-crud-bean-validation

  • Description: Uma aplicação CRUD simples com Spring Boot

  • Package name: com.example.simplecrud

Adicione também as seguintes dependências:

  • Web: Spring Web

  • Validação: Bean Validation

  • H2 Database: H2 Database

  • Spring Data JPA: Spring Data and Hibernate

Configuração do Spring Initializr mostrando um projeto CRUD em Java com Spring Boot, com dependências de Bean Validation, banco de dados H2, Spring Data JPA e Spring Web

Clique em Generate para baixar o projeto em um arquivo ZIP. Em seguida, extraia o arquivo e importe o projeto na IDE de sua preferência.

Implemente a aplicação

Vamos seguir a estrutura padrão de projetos Spring Boot. Veja a estrutura completa do projeto:

Árvore de diretórios do projeto de uma aplicação simples de validação de beans com operações CRUD, mostrando arquivos Java de código-fonte, modelo, repositório, controlador e teste, além de arquivos Maven.

Modelo User

Para implementar a aplicação, comece criando o diretório model em src/main/java/com/example/simplecrudbeanvalidation. Em seguida, dentro do diretório model, crie um arquivo chamado User.java e adicione os campos e as anotações do Bean Validation a seguir à classe User:

import jakarta.persistence.*;
import jakarta.validation.constraints.*;

@Entity
@Table(name="users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @NotEmpty
    @Pattern(regexp = "[a-zA-Z0-9 ]")
    private String name;

    @NotNull
    @NotEmpty
    @Email
    @Pattern(regexp=".+@.+\\..+")
    private String email;

    @NotNull
    @NotEmpty
    @Size(min = 8, max = 64)
    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public User(Long id, String name, String email, String password){
        this.id = id;
        this.name = name;
        this.email = email;
        this.password = password;
    }

    //default constructor
    public User(){}
}

Este código define uma classe Java chamada User, que representa uma tabela chamada users em um banco de dados, usando anotações da Jakarta Persistence API (JPA). Ela tem quatro variáveis de instância — id, name, email e password — com getters e setters correspondentes. Cada campo tem determinadas restrições de validação, como @NotNull, @NotEmpty, @Email, @Size e @Pattern. @Pattern indica que a senha deve ter de oito a 64 caracteres e conter pelo menos um dígito, uma letra minúscula, uma letra maiúscula e um caractere especial. Além disso, @Pattern também é usado na propriedade name para evitar injeções de scripts. A classe pode ser usada como modelo para armazenar e recuperar dados de usuários em um banco de dados.

Repositório User

Para lidar com operações CRUD usando um banco de dados em memória, crie o diretório repository dentro de src/main/java/com/example/simplecrudbeanvalidation. Em seguida, dentro do diretório directory, crie um arquivo chamado UserRepository.java para definir a interface UserRepository, conforme abaixo:

import com.example.simplecrudbeanvalidation.model.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends CrudRepository<User, Long> {
}

Este código define uma interface Java chamada UserRepository, que estende a interface CrudRepository fornecida pelo Spring Data JPA. A interface UserRepository especifica dois parâmetros genéricos para CrudRepository: User, o tipo de entidade gerenciada por este repositório, e Long, o tipo da chave primária da entidade.

Ao estender CrudRepository, UserRepository herda vários métodos para realizar operações CRUD na entidade User. Esses métodos podem ser usados para interagir com um banco de dados em memória e gerenciar objetos User sem precisar de configurações complexas ou de um servidor de banco de dados completo.

Controller User

Para criar a classe UserController, que define os endpoints da API RESTful e a lógica de validação, comece criando um diretório chamado controller dentro de src/main/java/com/example/simplecrudbeanvalidation. No diretório controller, crie um arquivo chamado UserController.java com o seguinte conteúdo:

import com.example.simplecrudbeanvalidation.model.User;
import com.example.simplecrudbeanvalidation.repository.UserRepository;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/users")
@Validated
public class UserController
{
   @Autowired
   private UserRepository userRepository;

   @GetMapping
   public List<User> getAllUsers() {
       return (List<User>) userRepository.findAll();
   }

   @GetMapping("/{id}")
   public ResponseEntity<User> getUserById(@PathVariable Long id) {
       Optional<User> userOptional = userRepository.findById(id);
       if (userOptional.isPresent()) {
           return ResponseEntity.ok(userOptional.get());
       } else {
           return ResponseEntity.notFound().build();
       }
   }

   @PostMapping
   public User createUser(@Valid @RequestBody User user) {
       return userRepository.save(user);
   }

   @PutMapping("/{id}")
   public ResponseEntity<User> updateUser(@PathVariable Long id, @Valid @RequestBody User updatedUser) {
       Optional<User> userOptional = userRepository.findById(id);
       if (userOptional.isPresent()) {
           User user = userOptional.get();
           user.setName(updatedUser.getName());
           user.setEmail(updatedUser.getEmail());
           user.setPassword(updatedUser.getPassword());
           userRepository.save(user);
           return ResponseEntity.ok(user);
       } else {
           return ResponseEntity.notFound().build();
       }
   }

   @DeleteMapping("/{id}")
   public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
       if (userRepository.existsById(id)) {
           userRepository.deleteById(id);
           return ResponseEntity.ok().build();
       } else {
           return ResponseEntity.notFound().build();
       }
   }
}

Este código define uma classe Spring Boot RestController chamada UserController, que gerencia solicitações HTTP para administrar objetos User. A classe contém cinco métodos que tratam de diferentes solicitações HTTP: recuperar todos os usuários, recuperar um usuário pelo ID, criar um usuário, atualizar um usuário existente e excluir um usuário.

A anotação @Autowired injeta a dependência UserRepository na classe UserController. A classe UserController é associada ao endpoint /api/users por meio da anotação @RequestMapping, que direciona as solicitações HTTP recebidas para este controller. Além disso, a anotação @Validated habilita a validação dos parâmetros da solicitação. Em conjunto, esse código fornece uma API RESTful para operações CRUD em objetos User, usando Spring Data e um banco de dados em memória.

Configure o banco de dados em memória

Para configurar o banco de dados em memória, abra o arquivo application.properties em src/main/resources/ e adicione as seguintes configurações:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Esta configuração do Spring Boot cria um banco de dados H2 em memória chamado testdb e configura seu driver, nome de usuário e senha. Ela também especifica o dialeto H2 a ser usado nas operações de banco de dados com Hibernate.

H2 não é recomendado para produção

Não recomendamos usar o H2 como banco de dados de produção, nem utilizar o nome de usuário e a senha padrão em um ambiente de produção. Embora essa configuração seja adequada para demonstrações, ela não deve ser usada em produção.

Teste unitário do Bean Validation

Depois de configurar o banco de dados em memória, você pode começar a testar o Bean Validation com testes unitários. Para isso, crie o diretório controller dentro de src/test/java/com/example/simplecrudbeanvalidation. Em seguida, dentro do diretório controller, crie um arquivo chamado UserControllerTest.java e escreva testes para verificar se as restrições do Bean Validation estão funcionando como esperado.

Na classe UserControllerTest, adicione o seguinte código:

import com.example.simplecrudbeanvalidation.model.User;
import com.example.simplecrudbeanvalidation.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Arrays;
import java.util.Optional;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserRepository userRepository;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void createUser_validData_success() throws Exception {
        User newUser = new User(null, "John Doe", "john.doe@example.com", "p@ssW0rd!");
        User savedUser = new User(1L, newUser.getName(), newUser.getEmail(), newUser.getPassword());

        when(userRepository.save(any(User.class))).thenReturn(savedUser);

        mockMvc.perform(post("/api/users")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(newUser)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(savedUser.getId()))
                .andExpect(jsonPath("$.name").value(savedUser.getName()))
                .andExpect(jsonPath("$.email").value(savedUser.getEmail()))
                .andExpect(jsonPath("$.password").value(savedUser.getPassword()));
    }

    @Test
    public void createUser_invalidData_failure() throws Exception {
        User invalidUser = new User(null, "", "invalid-email", "123");

        mockMvc.perform(post("/api/users")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(invalidUser)))
                .andExpect(status().isBadRequest());
    }

    @Test
    public void getAllUsers() throws Exception {
        User user1 = new User(1L, "John Doe", "john.doe@example.com", "p@ssW0rd!");
        User user2 = new User(2L, "Jane Doe", "jane.doe@example.com", "p@ssW0rd!");
        when(userRepository.findAll()).thenReturn(Arrays.asList(user1, user2));
        mockMvc.perform(get("/api/users"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].id").value(user1.getId()))
                .andExpect(jsonPath("$[0].name").value(user1.getName()))
                .andExpect(jsonPath("$[0].email").value(user1.getEmail()))
                .andExpect(jsonPath("$[0].password").value(user1.getPassword()))
                .andExpect(jsonPath("$[1].id").value(user2.getId()))
                .andExpect(jsonPath("$[1].name").value(user2.getName()))
                .andExpect(jsonPath("$[1].email").value(user2.getEmail()))
                .andExpect(jsonPath("$[1].password").value(user2.getPassword()));
    }

    @Test
    public void getUserById_found() throws Exception {
        User user = new User(1L, "John Doe", "john.doe@example.com", "p@ssW0rd!");
        when(userRepository.findById(user.getId())).thenReturn(Optional.of(user));
        mockMvc.perform(get("/api/users/{id}", user.getId()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(user.getId()))
                .andExpect(jsonPath("$.name").value(user.getName()))
                .andExpect(jsonPath("$.email").value(user.getEmail()))
                .andExpect(jsonPath("$.password").value(user.getPassword()));
    }

    @Test
    public void getUserById_notFound() throws Exception {
        Long userId = 1L;
        when(userRepository.findById(userId)).thenReturn(Optional.empty());
        mockMvc.perform(get("/api/users/{id}", userId))
                .andExpect(status().isNotFound());
    }
}

Este código reúne testes JUnit para a classe UserController. Os testes usam a classe MockMvc para simular solicitações HTTP ao endpoint /api/users e verificar se os métodos correspondentes da classe UserController as tratam corretamente. Os cenários incluem criar um usuário com dados válidos e inválidos, recuperar todos os usuários e recuperar um usuário pelo ID, tanto quando ele existe quanto quando não existe.

A anotação @SpringBootTest carrega o contexto da aplicação, e a anotação @MockBean cria um mock da dependência UserRepository da classe UserController. No conjunto, esses testes garantem que a classe UserController funcione corretamente e responda como esperado a diferentes solicitações HTTP.

Para executar os testes, abra um terminal ou shell, acesse o diretório raiz do projeto e rode o seguinte comando:

mvn test

Você verá que todos os testes passaram:

…output omitted…
[INFO] Results:
[INFO] 
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
…output omitted…

Execute e teste suas APIs

No IntelliJ, clique com o botão direito no arquivo SimpleCrudBeanValidationApplication.java e selecione Run 'SimpleCrudBeanValidationApplication.main()':

O IntelliJ IDEA exibe uma classe Java User com anotações de validação de beans e um menu de contexto que destaca a opção de executar a aplicação.

Outra opção é executar a aplicação no terminal ou shell com os seguintes comandos:

mvn clean install
java -jar target/simple-crud-bean-validation-0.0.1-SNAPSHOT.jar

Depois de iniciar a aplicação Spring Boot, você pode testar as operações CRUD usando comandos curl no terminal ou shell, de forma semelhante a uma aplicação de front-end ou ao Postman ao chamar essas APIs.

Para testar as operações CRUD, comece criando um usuário:

curl -X POST -H "Content-Type: application/json" -d '{"name":"John Doe","email":"john.doe@example.com","password":"p@ssW0rd!"}' http://localhost:8080/api/users

A saída será semelhante a esta:

{"id":1,"name":"John Doe","email":"john.doe@example.com","password":"p@ssW0rd!"} 

Em seguida, liste todos os usuários:

curl -X GET http://localhost:8080/api/users

A saída será semelhante a esta:

{"id":1,"name":"John Doe","email":"john.doe@example.com","password":"p@ssW0rd!"}

Agora, busque o usuário pelo ID. Substitua <id> pelo ID do usuário que você quer recuperar:

curl -X GET http://localhost:8080/api/users/<id>

A saída será:

{"id":1,"name":"John Doe","email":"john.doe@example.com","password":"p@ssW0rd!"}

Atualize o usuário, substituindo <id> pelo ID do usuário que você quer atualizar:

curl -X PUT -H "Content-Type: application/json" -d '{"name":"John Doe Updated","email":"john.doe.updated@example.com","password":"newP@ssW0rd!"}' http://localhost:8080/api/users/<id>

A saída será semelhante a esta:

{"id":1,"name":"John Doe Updated","email":"john.doe.updated@example.com","password":"newP@ssW0rd!"}

Em seguida, exclua um usuário, substituindo <id> pelo ID do usuário que você quer excluir:

curl -X DELETE http://localhost:8080/api/users/<id>

Este comando não gera nenhuma saída.

Com estes comandos curl, você pode testar as operações CRUD da sua API RESTful de usuários. Lembre-se de substituir <id> pelo ID de usuário apropriado em cada comando. Seguindo estas etapas, você pode interagir com sua aplicação Spring Boot e verificar se as restrições do Bean Validation estão funcionando como esperado durante a execução.

Se você executar estas solicitações curl novamente com dados incorretos, como um e-mail inválido, uma senha inválida ou valores vazios, verá que a API retorna um erro de solicitação inválida. Essa mensagem indica que a validação no nível do controller, também conhecida como Bean Validation, não foi aprovada.

Vamos ver alguns exemplos de solicitações inválidas. Aqui, você cria um usuário com um endereço de e-mail inválido:

curl -X POST -H "Content-Type: application/json" -d '{"name":"John Doe","email":"johexample","password":"p@ssW0rd!"}' http://localhost:8080/api/users

A saída será semelhante a esta:

{"timestamp":"2023-03-28T10:27:21.884+00:00","status":400,"error":"Bad Request","path":"/api/users"}

Agora, você cria um usuário com uma senha inválida:

curl -X POST -H "Content-Type: application/json" -d '{"name":"John Doe","email":"joh@example.com","password":"test"}' http://localhost:8080/api/users

A saída será a seguinte:

{"timestamp":"2023-03-28T10:29:30.793+00:00","status":400,"error":"Bad Request","path":"/api/users"}

Concluindo a validação

Neste artigo, você aprendeu a implementar o Bean Validation do Spring Boot em uma aplicação CRUD simples. Você criou um projeto com o Spring Initializr, adicionou as dependências necessárias e implementou um modelo de usuário com anotações do Bean Validation. Também implementou um UserRepository e um UserController com endpoints de API RESTful que usam o Bean Validation, além de escrever testes para verificar se as restrições do Bean Validation estão funcionando como esperado.

Para reforçar a segurança da sua aplicação, use o Snyk Code, uma ferramenta SAST que detecta padrões de código inseguros capazes de levar a ataques de injeção. Você pode usar a extensão plugin Snyk Security para IntelliJ IDE para integrar essa análise ao seu fluxo de desenvolvimento.

Agora que você entende como funciona o Bean Validation do Spring Boot, pode implementá-lo nos seus próprios projetos para garantir a integridade dos dados e melhorar a experiência dos usuários. Para conferir o código completo, acesse o seguinte repositório no GitHub.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.

Leia mais

blog feature snyk container custom base recommendations
Blog

Conheça o Evo Agentic AppSec: segurança que acompanha o ritmo do desenvolvimento com IA

O desenvolvimento impulsionado por IA está ultrapassando os fluxos de trabalho tradicionais de segurança. O Agentic AppSec da Snyk usa agentes de segurança para encontrar, corrigir e verificar vulnerabilidades continuamente, enquanto as pessoas definem os critérios.

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

illustration hero ai
Blog

O furacão da IA chegou

A IA está acelerando tanto a criação de software quanto os ataques cibernéticos. As lideranças devem proteger agentes e código desde o início, aplicar controles em tempo de execução e validar as defesas de forma independente.