GNU/Linux >> Tutoriels Linux >  >> Cent OS

Comment implémenter la validation pour les services RESTful avec Spring

Validation du haricot de printemps

La validation des données n'est pas un sujet nouveau dans le développement d'applications Web.

Nous examinons brièvement la validation des données dans l'écosystème Java en général et dans Spring Framework en particulier. La plate-forme Java est devenue la norme de facto pourla mise en œuvre de la validation des données c'est la spécification de validation de bean. La spécification Bean Validation a plusieurs versions :

  • 1.0 (JSR-303),
  • 1.1 (JSR-349),
  • 2.0 (JSR 380) – la dernière version

Cette spécification définit un ensemble de composants, d'interfaces et d'annotations. Cela fournit un moyen standard de mettre des contraintes sur les paramètres et de renvoyer les valeurs des méthodes et des paramètres des constructeurs, de fournir une API pour valider les objets et les graphiques d'objets.

Un modèle déclaratif est utilisé pour mettre des contraintes sous forme d'annotations sur les objets et leurs champs. Il existe des annotations prédéfinies comme @NotNull , @Digits , @Pattern , @Email , @CreditCard . Il est possible de créer de nouvelles contraintes personnalisées.

La validation peut s'exécuter manuellement ou plus naturellement, lorsque d'autres spécifications et cadres valident les données au bon moment, par exemple, une entrée utilisateur, une insertion ou une mise à jour dans JPA.

Validation dans l'exemple Java

Voyons comment cela peut être fait en pratique dans cet exemple simple de validation de bean dans une application Java standard.

Nous avons un objet que nous voulons valider avec tous les champs annotés avec des contraintes.

public class SimpleDto {

  @Min(value = 1, message = "Id can't be less than 1 or bigger than 999999")
  @Max(999999)
  private int id;

  @Size(max = 100)
  private String name;

  @NotNull
  private Boolean active;

  @NotNull
  private Date createdDatetime;

  @Pattern(regexp = "^asc|desc$")
  private String order = "asc";

  @ValidCategory(categoryType="simpleDto")
  private String category;
  …
  Constructor, getters and setters

Nous pouvons maintenant l'utiliser dans une simple application Java et valider manuellement l'objet.

public class SimpleApplication {

  public static void main(String[] args) {

    final SimpleDto simpleDto = new SimpleDto();
    simpleDto.setId(-1);
    simpleDto.setName("Test Name");
    simpleDto.setCategory("simple");
    simpleDto.setActive(true);
    simpleDto.setOrder("asc");
    simpleDto.setCreatedDatetime(new Date());

    ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
    Validator validator = validatorFactory.usingContext().getValidator();

    Set constrains = validator.validate(simpleDto);
    for (ConstraintViolation constrain : constrains) {

       System.out.println(
      "[" + constrain.getPropertyPath() + "][" + constrain.getMessage() + "]"
      );

    }

  }

}

Et le résultat dans la console sera :

“[id] [Id can't be less than 1 or bigger than 999999]”

Contrainte de validation et annotation

Créez une contrainte de validation personnalisée et une annotation.


 @Retention(RUNTIME)
   @Target(FIELD)
   @Constraint(validatedBy = {ValidCategoryValidator.class})
   public @interface ValidCategory {

      String categoryType();

      String message() default "Category is not valid";

      Class<?>[] groups() default {};

      Class<? extends Payload>[] payload() default {};

    }

And constraint validation implementation:

public class ValidCategoryValidator implements ConstraintValidator<ValidCategory, String> {

    private static final Map<String, List> availableCategories;

    static {

      availableCategories = new HashMap<>();
      availableCategories.put("simpleDto", Arrays.asList("simple", "advanced"));

    }

    private String categoryType;

    @Override
    public void initialize(ValidCategory constraintAnnotation) {

      this.setCategoryType(constraintAnnotation.categoryType());

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

      List categories = ValidCategoryValidator.availableCategories.get(categoryType);
      if (categories == null || categories.isEmpty()) {

         return false;

      }

      for (String category : categories) {

         if (category.equals(value)) {

             return true;

      }

    }

    return false;

  }

}

Dans l'exemple ci-dessus, les catégories disponibles proviennent d'une simple carte de hachage. Dans des cas d'utilisation réels d'applications, ils peuvent être récupérés à partir de la base de données ou de tout autre service.

Veuillez noter que les contraintes et les validations peuvent être spécifiées et effectuées non seulement au niveau du champ, mais également sur un objet entier.

Lorsque nous devons valider diverses dépendances de champs, par exemple, la date de début, cela ne peut pas être après la date de fin.

Les implémentations les plus largement utilisées de Bean Validation les spécifications sont Hibernate Validator et Apache BVal .

Validation avec Spring

Le framework Spring fournit plusieurs fonctionnalités de validation.

  • La prise en charge des versions 1.0, 1.1 (JSR-303, JSR-349) de l'API Bean Validation a été introduite dans Spring Framework à partir de la version 3.
  • Spring a sa propre interface Validator qui est très basique et peut être définie dans une instance DataBinder spécifique. Cela pourrait être utile pour implémenter une logique de validation sans annotations.

Validation du bean avec Spring

Spring Boot fournit une validation démarrée qui peut être incluse dans le projet :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
 </dependency>

Ce starter fournit une version de Hibernate Validator compatible avec le Spring Boot actuel.

En utilisant Bean Validation, nous pourrions valider un corps de requête, des paramètres de requête, des variables dans le chemin (par exemple, / /simpledto/{id} ), ou n'importe quel paramètre de méthode ou de constructeur.

Requêtes POST ou PUT

Dans les requêtes POST ou PUT, par exemple, nous transmettons la charge utile JSON, Spring la convertit automatiquement en objet Java et nous voulons maintenant valider l'objet résultant. Utilisons SimpleDto objet du 1 exemple :

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

    @Autowired
    private SimpleDtoService simpleDtoService;

    @RequestMapping(path = "", method = RequestMethod.POST, produces = 
    "application/json")
    public SimpleDto createSimpleDto(

        @Valid @RequestBody SimpleDto simpleDto) {

      SimpleDto result = simpleDtoService.save(simpleDto);

      return result;

    }

}

Nous venons d'ajouter @Valid annotation au SimpleDto paramètre annoté avec @RequestBody . Cela indiquera à Spring de traiter la validation avant de faire un appel de méthode réel. En cas d'échec de la validation, Spring lancera une MethodArgument NotValidException qui, par défaut, renverra une réponse 400 (Bad Request).

Validation des variables de chemin

La validation des variables de chemin fonctionne un peu différemment. Le problème est que nous devons maintenant ajouter des annotations de contrainte directement aux paramètres de méthode plutôt qu'à l'intérieur des objets.

Pour que cela fonctionne, il y a 2 options possibles :

Option 1 :@Annotation validée

Ajouter @Validated annotation au contrôleur au niveau de la classe pour évaluer les annotations de contrainte sur les paramètres de méthode.

Option 2 :Variable de chemin

Utilisez un objet qui représente la variable de chemin comme dans l'exemple ci-dessous :

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

    @Autowired
    private SimpleDtoService simpleDtoService;

    @RequestMapping(path = "/{simpleDtoId}", method = RequestMethod.GET, produces = 
    "application/json")
    public SimpleDto getSimpleDto(

      @Valid SimpleDtoIdParam simpleDtoIdParam) {

    SimpleDto result = simpleDtoService.findById(simpleDtoIdParam.getSimpleDtoId());

    if (result == null) {

      throw new NotFoundException();

    }

    return result;

  }

}

Dans ce cas, nous avons SimpleDtoIdParam classe qui contient le simpleDtoId champ qui sera validé par rapport à une annotation de contrainte Bean standard ou personnalisée. Chemin Nom de la variable (/{simpleDtoId} ) doit être le même que le nom du champ (pour que Spring puisse trouver le setter pour ce champ).

private static final long serialVersionUID = -8165488655725668928L;

    @Min(value = 1)
    @Max(999999)
    private int simpleDtoId;

    public int getSimpleDtoId() {
    return simpleDtoId;
    }

    public void setSimpleDtoId(int simpleDtoId) {
    this.simpleDtoId = simpleDtoId;
    }

}

Contrairement à Request Body validation, variable de chemin la validation lève ConstraintViolationException au lieu de MethodArgumentNotValidException . Par conséquent, nous devrons créer un gestionnaire d'exceptions personnalisé.

Le framework Java Spring permet également de valider les paramètres au niveau du service avec @Validated annotation (niveau classe) et @Valid (niveau paramètre).

Étant donné que Spring JPA utilise Hibernate en dessous, il prend également en charge la validation de bean pour les classes d'entités. Veuillez noter que ce n'est probablement pas une bonne idée dans de nombreux cas de s'appuyer sur ce niveau de validation, car cela signifie que toute la logique auparavant traitait d'objets non valides.

Interface de validation du printemps

Spring définit sa propre interface pour le validateur de validation (org.springframework.validation.Validator). Il peut être défini pour une instance DataBinder spécifique et implémenter la validation sans annotations (approche non déclarative).

Pour mettre en œuvre cette approche, nous aurions besoin :

  1. Mettre en œuvre l'interface de validation
  2. Ajouter un validateur

Mettre en œuvre l'interface du validateur

Implémenter l'interface Validator, par exemple, permet de travailler avec notre SimpleDto classe :

@Component
public class SpringSimpleDtoValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
    return SimpleDto.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {

      if (errors.getErrorCount() == 0) {

        SimpleDto param = (SimpleDto) target;
        Date now = new Date();
        if (param.getCreatedDatetime() == null) {

          errors.reject("100",

            "Create Date Time can't be null");

        } else if (now.before(param.getCreatedDatetime())) {

          errors.reject("101",

            "Create Date Time can't be after current date time");

        }

      }

    }

}

Vérifiez ici si le Datetime créé l'horodatage est dans le futur.

Ajouter un validateur

Ajouter l'implémentation du validateur à DataBinder :

@RestController
@RequestMapping("/simpledto")
public class SimpleDtoController {

    @Autowired
    private SimpleDtoService simpleDtoService;

    @Autowired
    private SpringSimpleDtoValidator springSimpleDtoValidator;

    @InitBinder("simpleDto")
    public void initMerchantOnlyBinder(WebDataBinder binder) {
    binder.addValidators(springSimpleDtoValidator);
    }

    @RequestMapping(path = "", method = RequestMethod.POST, produces = 
    "application/json")
    public SimpleDto createSimpleDto(

      @Valid @RequestBody SimpleDto simpleDto) {

    SimpleDto result = simpleDtoService.save(simpleDto);
    return result;

  }

}

Nous avons maintenant SimpleDto validé à l'aide d'annotations de contraintes et de notre implémentation personnalisée de Spring Validation.


Cent OS
  1. Comment gérer les services Systemd avec Systemctl sous Linux

  2. Comment analyser un serveur Debian à la recherche de rootkits avec Rkhunter

  3. Comment créer une boucle For avec un nombre variable d'itérations ?

  4. Comment configurer IMAP avec SSL

  5. Comment réécrire des URL avec mod_rewrite pour Apache sur Ubuntu 20.04

Comment intégrer Grafana à Prometheus pour la surveillance

Comment rechercher des virus avec ClamAV sur Ubuntu 20.04

Comment exécuter des pods en tant que services systemd avec Podman

Comment configurer Nginx avec SSL

Comment configurer Redis pour une haute disponibilité avec Sentinel dans CentOS 8 - Partie 2

Comment automatiser les audits de sécurité Docker avec Docker Bench for Security