Skip to main content

Guide de validation des entrées avec Spring Boot

Écrit par
Headshot of Lucien Chemaly

Lucien Chemaly

feature bean validation

12 septembre 2023

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

Si vous développez en Java, vous connaissez probablement Spring Boot, le framework robuste qui simplifie le développement d’applications autonomes basées sur Spring et adaptées à la production. Parmi ses nombreuses fonctionnalités figure Bean Validation, un élément essentiel de toute application pour garantir l’intégrité des données et améliorer l’expérience utilisateur.

Dans les applications Spring Boot, Bean Validation peut servir à valider les entrées et les formulaires, à vérifier les données avant leur enregistrement dans une base de données et à appliquer différentes politiques de sécurité. Grâce à Bean Validation, les développeurs peuvent prévenir les erreurs, améliorer la qualité globale de l’application et garantir la cohérence des données dans toute l’application.

Dans cet article, vous découvrirez les différentes utilisations de Bean Validation et comment l’implémenter dans Spring Boot, afin de pouvoir l’exploiter efficacement dans vos propres projets.

Qu’est-ce que Bean Validation ?

Bean Validation permet d’appliquer des contraintes aux modèles de données de votre application afin de vérifier que les données respectent certaines règles avant leur traitement ou leur stockage. Cela repose sur l’intégration de Jakarta Bean Validation, qui fournit un ensemble d’annotations et d’interfaces pour définir et vérifier les contraintes.

En appliquant Bean Validation, les développeurs peuvent gagner du temps et économiser des ressources en détectant les erreurs et les incohérences dès les premières étapes du développement. De plus, Bean Validation contribue à améliorer l’expérience utilisateur globale en réduisant la quantité de données incorrectes saisies.

Examinons quelques cas d’utilisation courants de Bean Validation dans le développement d’applications :

Validation des entrées et des formulaires

La validation des entrées et des formulaires est l’un des cas d’utilisation les plus courants de Bean Validation. Elle permet de vérifier que les utilisateurs saisissent des données au bon format (par exemple, validation d’un formulaire d’inscription contenant une adresse e-mail, un nom d’utilisateur et un mot de passe).

Validation des données avant leur enregistrement dans une base de données

Bean Validation permet également de valider les données avant leur enregistrement dans une base de données. Cette méthode garantit la cohérence et l’exactitude des données saisies. Elle contribue ainsi à prévenir les erreurs et à réduire le risque d’altération des données.

Application des politiques de sécurité

Bean Validation peut également servir à appliquer des politiques de sécurité en validant les entrées utilisateur ou les données extraites de la base de données, afin de vérifier qu’elles répondent à des critères précis, comme les exigences de robustesse des mots de passe. La validation joue un rôle essentiel dans la prévention des attaques par injection et contribue à empêcher des vulnérabilités telles que le cross-site scripting (XSS) stocké.

Validation de la logique métier

Bean Validation peut servir à vérifier la logique métier et à s’assurer qu’elle répond à des exigences ou à des normes précises. En définissant des contraintes de validation, comme la complexité des mots de passe ou la validité des quantités et des prix des articles commandés, Bean Validation peut vérifier automatiquement les données et appliquer les règles requises. Cela évite les vérifications manuelles et garantit que l’application respecte les normes ou exigences définies.

Implémenter Bean Validation dans Spring Boot

Dans ce tutoriel, vous allez implémenter Bean Validation dans une application simple de création, lecture, mise à jour et suppression (CRUD) utilisant une base de données en mémoire. Les utilisateurs fournissent leur nom, leur adresse e-mail et leur mot de passe, qui doivent répondre à certains critères pour être acceptés. L’architecture générale présentée ici comprend un serveur backend avec des API REST et une base de données H2 en mémoire.

Lorsqu’une application cliente appelle le service web (l’application créée dans ce tutoriel), la requête est envoyée à UserController. Le contrôleur valide ensuite les données de la requête. Si elles sont valides, la requête est transmise à UserRepository, qui communique avec la base de données en mémoire et renvoie la réponse correspondante. Sinon, les données ne sont pas valides et un message d’erreur est renvoyé au client :

Schéma de flux montrant une requête client passant par la validation des beans de UserController jusqu’à UserRepository, avec un chemin valide et un chemin d’erreur de validation.

Prérequis

Avant de commencer l’implémentation, vérifiez que les outils et technologies suivants sont installés :

Créer un projet Spring Boot

Pour créer un projet Spring Boot, rendez-vous sur Spring Initializr et sélectionnez les options suivantes :

  • Type de projet : Maven Project

  • Langage : Java

  • Packaging : Jar

  • Version de Java : 17

  • Spring Boot : 3.1.0 (SNAPSHOT)

Saisissez les informations suivantes dans la section Project Metadata :

  • Group : com.example

  • Artifact : simple-crud-bean-validation

  • Name : simple-crud-bean-validation

  • Description : Une application CRUD simple avec Spring Boot

  • Package name : com.example.simplecrud

Ajoutez également les dépendances suivantes :

  • Web : Spring Web

  • Validation : Bean Validation

  • H2 Database : H2 Database

  • Spring Data JPA : Spring Data and Hibernate

Configuration de Spring Initializr présentant un projet CRUD Java avec Spring Boot, les dépendances Bean Validation, H2 Database, Spring Data JPA et Spring Web

Cliquez sur Generate pour télécharger le projet au format ZIP. Extrayez ensuite le fichier ZIP et importez le projet dans l’IDE de votre choix.

Implémenter l’application

Nous allons suivre la structure de projet standard des applications Spring Boot. Voici la structure complète du projet :

Arborescence du répertoire d’un projet d’application simple de validation des beans CRUD, avec les fichiers Java source, modèle, dépôt, contrôleur et test, ainsi que les fichiers Maven.

Modèle User

Pour implémenter l’application, commencez par créer le répertoire model dans src/main/java/com/example/simplecrudbeanvalidation. Dans le répertoire model, créez ensuite un fichier nommé User.java, puis ajoutez les champs et annotations Bean Validation suivants à la 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(){}
}

Ce code définit une classe Java nommée User qui représente une table de base de données appelée users, à l’aide des annotations Jakarta Persistence API (JPA). Elle comporte quatre variables d’instance — id, name, email et password — dotées de leurs getters et setters. Chacun de ces champs est soumis à certaines contraintes de validation, telles que @NotNull, @NotEmpty, @Email, @Size et @Pattern. @Pattern indique que le mot de passe doit compter entre huit et soixante-quatre caractères et contenir au moins un chiffre, une lettre minuscule, une lettre majuscule et un caractère spécial. De plus, @Pattern est également appliqué à la propriété name pour prévenir les injections de scripts. Cette classe peut servir de modèle pour stocker et récupérer des données utilisateur dans une base de données.

Dépôt User

Pour gérer les opérations CRUD avec une base de données en mémoire, créez le répertoire repository dans src/main/java/com/example/simplecrudbeanvalidation. Dans le répertoire directory, créez ensuite un fichier nommé UserRepository.java afin de définir l’interface UserRepository, comme suit :

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

Ce code définit une interface Java appelée UserRepository qui étend l’interface CrudRepository fournie par Spring Data JPA. L’interface UserRepository spécifie deux paramètres génériques pour CrudRepository : User, le type d’entité géré par ce dépôt, et Long, le type de clé primaire de l’entité.

En étendant CrudRepository, UserRepository hérite de plusieurs méthodes permettant d’effectuer des opérations CRUD sur l’entité User. Ces méthodes permettent d’interagir avec une base de données en mémoire pour gérer des objets User, sans configuration complexe ni serveur de base de données complet.

Contrôleur User

Pour créer la classe UserController, qui définit les points de terminaison de l’API REST et la logique de validation, commencez par créer un répertoire nommé controller dans le répertoire src/main/java/com/example/simplecrudbeanvalidation. Dans le répertoire controller, créez un fichier nommé UserController.java et ajoutez le contenu suivant :

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

Ce code définit une classe Spring Boot RestController appelée UserController, qui gère les requêtes HTTP relatives aux objets User. La classe comporte cinq méthodes qui traitent différents types de requêtes HTTP : récupérer tous les utilisateurs, récupérer un utilisateur par son ID, créer un utilisateur, mettre à jour un utilisateur existant et supprimer un utilisateur.

L’annotation @Autowired sert à injecter la dépendance UserRepository dans la classe UserController. La classe UserController est associée au point de terminaison /api/users grâce à l’annotation @RequestMapping, qui associe les requêtes HTTP entrantes à ce contrôleur. En outre, l’annotation @Validated active la validation des paramètres de requête. Dans l’ensemble, ce code fournit une API REST pour effectuer des opérations CRUD sur des objets User avec Spring Data et une base de données en mémoire.

Configurer la base de données en mémoire

Pour configurer la base de données en mémoire, ouvrez le fichier application.properties dans src/main/resources/ et ajoutez les paramètres suivants :

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

Cette configuration Spring Boot configure une base de données H2 en mémoire nommée testdb et définit son pilote, son nom d’utilisateur et son mot de passe. Elle indique également le dialecte H2 à utiliser pour les opérations de base de données avec Hibernate.

H2 déconseillé en production

L’utilisation de H2 comme base de données de production est déconseillée. Le nom d’utilisateur et le mot de passe par défaut ne doivent pas être utilisés en production. Cette configuration convient aux démonstrations, mais ne doit pas être utilisée en production.

Tests unitaires de Bean Validation

Une fois la base de données en mémoire configurée, vous pouvez commencer les tests unitaires de Bean Validation. Pour cela, créez le répertoire controller dans src/test/java/com/example/simplecrudbeanvalidation. Dans le répertoire controller, créez ensuite un fichier nommé UserControllerTest.java et rédigez des tests pour vérifier que les contraintes Bean Validation fonctionnent comme prévu.

Dans la classe UserControllerTest, ajoutez le code suivant :

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

Ce code regroupe des tests JUnit pour la classe UserController. Ils utilisent la classe MockMvc pour simuler des requêtes HTTP vers le point de terminaison /api/users et vérifier que les méthodes correspondantes de la classe UserController les traitent correctement. Les tests couvrent notamment la création d’un utilisateur avec des données valides ou non, la récupération de tous les utilisateurs et la récupération d’un utilisateur par ID, qu’il existe ou non.

L’annotation @SpringBootTest sert à charger le contexte de l’application, tandis que l’annotation @MockBean permet de simuler la dépendance UserRepository de la classe UserController. Dans l’ensemble, ces tests vérifient que la classe UserController fonctionne correctement et répond comme prévu aux différentes requêtes HTTP.

Pour exécuter les tests, ouvrez un terminal ou un shell, accédez au répertoire racine de votre projet et lancez la commande suivante :

mvn test

Vous devriez constater que tous les tests ont réussi :

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

Exécuter et tester vos API

Dans IntelliJ, faites un clic droit sur le fichier SimpleCrudBeanValidationApplication.java, puis sélectionnez Run 'SimpleCrudBeanValidationApplication.main()' :

IntelliJ IDEA affiche une classe Java User avec des annotations de validation des beans et un menu contextuel mettant en évidence l’option permettant d’exécuter l’application.

Vous pouvez également exécuter votre application dans le terminal ou le shell à l’aide des commandes suivantes :

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

Après le démarrage de l’application Spring Boot, vous pouvez tester les opérations CRUD à l’aide de commandes curl dans le terminal ou le shell, comme le ferait une application frontend ou Postman pour appeler ces API.

Pour tester les opérations CRUD, commencez par créer un utilisateur :

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

Le résultat devrait ressembler à ceci :

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

Récupérez ensuite tous vos utilisateurs :

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

Le résultat devrait ressembler à ceci :

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

Récupérez ensuite un utilisateur par son ID. Remplacez <id> par l’ID de l’utilisateur que vous souhaitez récupérer :

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

Voici le résultat :

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

Mettez à jour l’utilisateur en remplaçant <id> par l’ID de l’utilisateur à modifier :

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>

Le résultat devrait ressembler à ceci :

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

Supprimez ensuite un utilisateur en remplaçant <id> par l’ID de l’utilisateur à supprimer :

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

Cette commande ne renvoie aucun résultat.

À l’aide de ces commandes curl, vous pouvez tester les opérations CRUD de votre API RESTful User. Pensez à remplacer <id> par l’ID utilisateur approprié dans chaque commande. En suivant ces étapes, vous pouvez interagir avec votre application Spring Boot et vérifier que les contraintes de Bean Validation fonctionnent comme prévu à l’exécution.

Si vous exécutez à nouveau ces requêtes curl avec des données incorrectes, comme une adresse e-mail invalide, un mot de passe incorrect ou des champs vides, l’API renverra une erreur de requête incorrecte. Ce message indique que la validation effectuée au niveau du contrôleur, également appelée Bean Validation, a échoué.

Voici quelques exemples de requêtes incorrectes. Ici, vous créez un utilisateur avec une adresse e-mail invalide :

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

Voici le résultat obtenu :

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

Ici, vous créez un utilisateur avec un mot de passe invalide :

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

Voici le résultat :

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

Pour conclure sur la validation

Dans cet article, vous avez appris à implémenter Bean Validation de Spring Boot dans une application CRUD simple. Vous avez créé un projet avec Spring Initializr, ajouté les dépendances nécessaires et implémenté un modèle utilisateur avec des annotations Bean Validation. Vous avez également implémenté un UserRepository et un UserController avec des points de terminaison d’API RESTful utilisant Bean Validation, puis écrit des tests pour vérifier que les contraintes Bean Validation fonctionnent comme prévu.

Pour renforcer la sécurité de votre application, utilisez Snyk Code, un outil SAST qui détecte les modèles de code non sécurisés susceptibles de provoquer des attaques par injection. Vous pouvez intégrer cette analyse à votre workflow de développement grâce à l’extension Snyk Security plugin for IntelliJ IDE.

Maintenant que vous comprenez le fonctionnement de Bean Validation dans Spring Boot, vous pouvez l’implémenter dans vos propres projets afin de garantir l’intégrité des données et d’améliorer l’expérience utilisateur. Pour découvrir le code final, consultez le dépôt GitHub suivant.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Publié dans:

Lire la suite

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les capacités des processus de sécurité traditionnels. Agentic AppSec de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

illustration hero ai
Blog

L’ouragan de l’IA est là

L’IA accélère à la fois la création de logiciels et les cyberattaques. Les dirigeants doivent sécuriser les agents et le code dès leur conception, appliquer des contrôles à l’exécution et valider les défenses de manière indépendante.