Guide pour les validateurs REST de données de printemps

Guide des validateurs REST de données de printemps

1. Vue d'ensemble

Cet article couvre une introduction aux bases des validateurs Spring Data REST. Si vous devez d'abord passer en revue les bases de Spring Data REST, visitez définitivementthis article pour vous familiariser avec les bases.

En termes simples, avec Spring Data REST, nous pouvons simplement ajouter une nouvelle entrée à la base de données via l’API REST, mais nous devons également nous assurer que les données sont valides avant de les conserver réellement.

Cet article continue sur unexisting article et nous réutiliserons le projet existant que nous y avons installé.

 

Et, si vous recherchez les premiersget started with Spring Data REST, voici un bon moyen de démarrer:

[.iframe-fluid] ##

2. Utilisation deValidators

À partir de Spring 3, le framework comporte l'interfaceValidator - qui peut être utilisée pour valider des objets.

2.1. Motivation

Dans l'article précédent, nous avons défini notre entité ayant deux propriétés -name etemail.

Et ainsi, pour créer une nouvelle ressource, nous devons simplement exécuter:

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "name" : "Test", "email" : "[email protected]" }'
  http://localhost:8080/users

Cette demande POST enregistrera l'objet JSON fourni dans notre base de données et l'opération renverra:

{
  "name" : "Test",
  "email" : "[email protected]",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

Un résultat positif était attendu puisque nous avions fourni des données valides. Mais que se passera-t-il si nous supprimons la propriéténame, ou si nous définissons simplement la valeur sur unString vide?

Pour tester le premier scénario, nous exécuterons la commande modifiée d'avant où nous définirons une chaîne vide comme valeur pour la propriéténame:

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users

Avec cette commande, nous obtiendrons la réponse suivante:

{
  "name" : "",
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

Pour le deuxième scénario, nous supprimerons la propriéténame de la requête:

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "email" : "Baggins" }' http://localhost:8080/users

Pour cette commande, nous obtiendrons cette réponse:

{
  "name" : null,
  "email" : "Baggins",
  "_links" : {
    "self" : {
        "href" : "http://localhost:8080/users/2"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/2"
    }
  }
}

Comme nous pouvons le voir, les deux demandes étaient OK et nous pouvons confirmer qu'avec 201 code de statut et lien API vers notre objet.

Ce comportement n'est pas acceptable car nous voulons éviter d'insérer des données partielles dans une base de données.

2.2. Événements REST Spring Data

Lors de chaque appel de l'API Spring Data REST, l'exportateur Spring Data REST génère divers événements répertoriés ci-dessous:

  • BeforeCreateEvent

  • AfterCreateEvent

  • BeforeSaveEvent

  • AfterSaveEvent

  • BeforeLinkSaveEvent

  • AfterLinkSaveEvent

  • BeforeDeleteEvent

  • AfterDeleteEvent

Puisque tous les événements sont traités de la même manière, nous montrerons seulement comment gérer lesbeforeCreateEvent qui sont générés avant qu'un nouvel objet ne soit enregistré dans la base de données.

2.3. Définition d'unValidator

Pour créer notre propre validateur, nous devons implémenter l'interfaceorg.springframework.validation.Validator avec les méthodessupports etvalidate.

Supports vérifie si le validateur prend en charge les requêtes fournies, tandis que la méthodevalidate valide les données fournies dans les requêtes.

Définissons une classeWebsiteUserValidator:

public class WebsiteUserValidator implements Validator {

    @Override
    public boolean supports(Class clazz) {
        return WebsiteUser.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        WebsiteUser user = (WebsiteUser) obj;
        if (checkInputString(user.getName())) {
            errors.rejectValue("name", "name.empty");
        }

        if (checkInputString(user.getEmail())) {
            errors.rejectValue("email", "email.empty");
        }
    }

    private boolean checkInputString(String input) {
        return (input == null || input.trim().length() == 0);
    }
}

L'objetErrors est une classe spéciale conçue pour contenir toutes les erreurs fournies dans la méthodevalidate. Plus loin dans cet article, nous montrerons comment vous pouvez utiliser les messages fournis contenus dans l'objetErrors. Pour ajouter un nouveau message d'erreur, nous devons appelererrors.rejectValue(nameOfField, errorMessage).

Une fois que nous avons défini le validateur, nous devons le mapper à un événement spécifique qui est généré une fois la demande acceptée.

Par exemple, dans notre cas,beforeCreateEvent est généré car nous voulons insérer un nouvel objet dans notre base de données. Mais comme nous voulons valider un objet dans une requête, nous devons d’abord définir notre validateur.

Cela peut être fait de trois manières:

  • Ajoutez l'annotationComponent avec le nom «beforeCreateWebsiteUserValidator». Spring Boot reconnaîtra le préfixebeforeCreate qui détermine l'événement que nous voulons attraper, et il reconnaîtra également la classeWebsiteUser à partir du nom deComponent.

    @Component("beforeCreateWebsiteUserValidator")
    public class WebsiteUserValidator implements Validator {
        ...
    }
  • CréezBean dans le contexte d'application avec l'annotation@Bean:

    @Bean
    public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
        return new WebsiteUserValidator();
    }
  • Enregistrement manuel:

    @SpringBootApplication
    public class SpringDataRestApplication
      extends RepositoryRestConfigurerAdapter {
        public static void main(String[] args) {
            SpringApplication.run(SpringDataRestApplication.class, args);
        }
    
        @Override
        public void configureValidatingRepositoryEventListener(
          ValidatingRepositoryEventListener v) {
            v.addValidator("beforeCreate", new WebsiteUserValidator());
        }
    }
    • Dans ce cas, vous n’avez pas besoin d’annotations sur la classeWebsiteUserValidator.

2.4. Bug de découverte d'événement

Pour le moment, unbug exists in Spring Data REST - qui affecte la découverte d'événements.

Si nous appelons la requête POST qui génère l'événementbeforeCreate, notre application n'appellera pas le validateur car l'événement ne sera pas découvert, à cause de ce bogue.

Une solution simple pour ce problème consiste à insérer tous les événements dans la classe Spring Data RESTValidatingRepositoryEventListener:

@Configuration
public class ValidatorEventRegister implements InitializingBean {

    @Autowired
    ValidatingRepositoryEventListener validatingRepositoryEventListener;

    @Autowired
    private Map validators;

    @Override
    public void afterPropertiesSet() throws Exception {
        List events = Arrays.asList("beforeCreate");
        for (Map.Entry entry : validators.entrySet()) {
            events.stream()
              .filter(p -> entry.getKey().startsWith(p))
              .findFirst()
              .ifPresent(
                p -> validatingRepositoryEventListener
               .addValidator(p, entry.getValue()));
        }
    }
}

3. Essai

DansSection 2.1., nous avons montré que, sans validateur, nous pouvons ajouter des objets sans propriété de nom dans notre base de données, ce qui n'est pas un comportement souhaité car nous ne vérifions pas l'intégrité des données.

Si nous voulons ajouter le même objet sans la propriéténame mais avec le validateur fourni, nous obtiendrons cette erreur:

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "email" : "[email protected]" }' http://localhost:8080/users
{
   "timestamp":1472510818701,
   "status":406,
   "error":"Not Acceptable",
   "exception":"org.springframework.data.rest.core.
    RepositoryConstraintViolationException",
   "message":"Validation failed",
   "path":"/users"
}

Comme nous pouvons le constater, les données manquantes de la demande ont été détectées et un objet n'a pas été enregistré dans la base de données. Notre demande a été renvoyée avec 500 codes HTTP et un message d'erreur interne.

Le message d'erreur ne dit rien sur le problème dans notre demande. Si nous voulons le rendre plus informatif, nous devrons modifier l'objet de réponse.

Dans lesException Handling in Spring article, nous avons montré comment gérer les exceptions générées par le framework, donc c'est certainement une bonne lecture à ce stade.

Puisque notre application génère une exceptionRepositoryConstraintViolationException, nous allons créer un gestionnaire pour cette exception particulière qui modifiera le message de réponse.

C'est notre classeRestResponseEntityExceptionHandler:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
  ResponseEntityExceptionHandler {

    @ExceptionHandler({ RepositoryConstraintViolationException.class })
    public ResponseEntity handleAccessDeniedException(
      Exception ex, WebRequest request) {
          RepositoryConstraintViolationException nevEx =
            (RepositoryConstraintViolationException) ex;

          String errors = nevEx.getErrors().getAllErrors().stream()
            .map(p -> p.toString()).collect(Collectors.joining("\n"));

          return new ResponseEntity(errors, new HttpHeaders(),
            HttpStatus.PARTIAL_CONTENT);
    }
}


Avec ce gestionnaire personnalisé, notre objet de retour aura des informations sur toutes les erreurs détectées.

4. Conclusion

Dans cet article, nous avons montré que les validateurs sont essentiels pour chaque API REST Spring Data, qui fournit une couche de sécurité supplémentaire pour l'insertion de données.

Nous avons également illustré combien il est simple de créer un nouveau validateur avec des annotations.

Comme toujours, le code de cette application se trouve dans lesGitHub project.