Cette fois, je vais vous présenter comment effectuer la validation (contrôle d'entrée) sur la valeur détenue par l'interface Pageable
fournie par Spring Data.
En utilisant Pageable
fourni par Spring Data, en plus des informations de page à rechercher (nombre de pages, nombre d'éléments affichés sur la page), les conditions de tri (propriété / colonne cible, ordre croissant / décroissant) peuvent être spécifiées dans les paramètres de la requête. Il est fonctionnellement pratique de pouvoir spécifier la condition de tri, mais si vous ne l'utilisez pas correctement, cela peut provoquer une injection SQL et ainsi de suite. Ce qu'il faut pour empêcher ce genre d'injection, c'est ... inutile de dire ... la validation.
Validez les arguments de la méthode Handler implémentée dans le Controller à l'aide de la fonction Method Validation (utilisant le mécanisme de validation Bean) fournie par Spring.
Plus précisément ... ça ressemble à ça.
@GetMapping
Page<Todo> search(@AllowedSortProperties({"id", "title"}) Pageable pageable) {
// ...
return page;
}
La validation de méthode fournie par Spring utilise la fonctionnalité de Spring AOP pour prendre en charge la validation des arguments d'appel de méthode et des valeurs de retour.
Ici, appliquez une validation qui vérifie si la propriété cible ou la colonne de la condition de tri se trouve dans la liste d'autorisation.
Comme toujours, créons un projet de développement Spring Boot à partir de "SPRING INITIALIZR". Dans ce cas, sélectionnez "Web" comme artefact de dépendance.
Ajoutez Spring Data Commons, qui contient «Pageable», aux bibliothèques dépendantes.
pom.xml
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
Lorsque vous ajoutez Spring Data Commons à vos bibliothèques dépendantes, le mécanisme AutoConfigure de Spring Boot active des fonctionnalités qui vous permettent de spécifier Pageable
comme argument de la méthode du gestionnaire du contrôleur (comme PageableHandlerMethodArgumentResolver
).
Créez une "annotation de contrainte" et une "classe d'implémentation pour ConstraintValidator
" pour vérifier "Pageable".
Annotation de contrainte pour Pageable
package com.example;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = {AllowedSortPropertiesValidator.class})
@Target({ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface AllowedSortProperties {
String message() default "{com.example.AllowedSortProperties.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
@Target({ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@interface List {
AllowedSortProperties[] value();
}
}
Classe d'implémentation de ConstraintValidator pour Pageable
package com.example;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class AllowedSortPropertiesValidator implements ConstraintValidator<AllowedSortProperties, Pageable> {
private Set<String> allowedSortProperties;
@Override
public void initialize(AllowedSortProperties constraintAnnotation) {
this.allowedSortProperties = new HashSet<>(Arrays.asList(constraintAnnotation.value()));
}
@Override
public boolean isValid(Pageable value, ConstraintValidatorContext context) {
if (value == null || value.getSort() == null) {
return true;
}
if (allowedSortProperties.isEmpty()) {
return true;
}
for (Sort.Order order : value.getSort()) {
if (!allowedSortProperties.contains(order.getProperty())) {
return false;
}
}
return true;
}
}
Appliquez la validation de méthode à l'appel de méthode du gestionnaire du contrôleur.
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@SpringBootApplication
public class BeanValidationDemoApplication {
public static void main(String[] args) {
SpringApplication.run(BeanValidationDemoApplication.class, args);
}
@Bean //Définition du bean du composant qui génère le validateur de la validation du bean
static LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
@Bean // Method Validation(AOP)Définition du bean du composant auquel
static MethodValidationPostProcessor methodValidationPostProcessor(LocalValidatorFactoryBean localValidatorFactoryBean) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
processor.setValidator(localValidatorFactoryBean);
return processor;
}
}
Créez une méthode de gestion qui reçoit Pageable
et spécifiez l'annotation de contrainte créée dans ↑. À ce moment, il est nécessaire d'ajouter «@ Validated» au niveau de la classe pour être la cible de la validation de méthode.
package com.example;
import org.springframework.data.domain.Pageable;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Validated //Si vous ne l'ajoutez pas, il ne sera pas soumis à la validation de méthode
@RestController
@RequestMapping("/todos")
public class TodoRestController {
@GetMapping
Pageable search(@AllowedSortProperties({"id", "title"}) Pageable pageable) {
return pageable;
}
}
Si c'est vrai, le processus de recherche est exécuté et la liste des objets de domaine est retournée, mais ici l'implémentation est telle que le "Pageable" reçu est retourné tel quel. (Parce que c'est une application de vérification uniquement ...: sweat_smile :)
Commençons par l'application Spring Boot et y accédons-y.
Comportement lorsqu'un nom de colonne de propriété dans la liste d'autorisation est spécifié
$ curl -D - http://localhost:8080/todos?sort=id
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 17 Dec 2016 15:51:51 GMT
{"sort":[{"direction":"ASC","property":"id","ignoreCase":false,"nullHandling":"NATIVE","ascending":true}],"offset":0,"pageNumber":0,"pageSize":20}
Comportement lorsqu'un nom de colonne de propriété qui n'est pas dans la liste d'autorisation est spécifié
$ curl -D - http://localhost:8080/todos?sort=createdAt
HTTP/1.1 500
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 17 Dec 2016 15:53:47 GMT
Connection: close
{"timestamp":1481990027135,"status":500,"error":"Internal Server Error","exception":"javax.validation.ConstraintViolationException","message":"No message available","path":"/todos"}
Cela semble fonctionner correctement, mais l'erreur lors de la spécification d'un nom de colonne de propriété qui n'est pas dans la liste d'autorisation est traitée comme une erreur de serveur interne 500. Normalement, ce devrait être 400 Bad Request.
Dans l'état par défaut, ce sera 500 Erreur de serveur interne, ajoutons donc la gestion des exceptions lorsqu'une erreur se produit dans la validation de méthode pour en faire 400 Bad Request.
package com.example;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.ConstraintViolationException;
@Validated
@RestController
@RequestMapping("/todos")
public class TodoRestController {
@GetMapping
Pageable search(@AllowedSortProperties({"id", "title"}) Pageable pageable) {
return pageable;
}
@ExceptionHandler //Ajout d'une méthode pour gérer ConstraintViolationException
@ResponseStatus(HttpStatus.BAD_REQUEST)
String handleConstraintViolationException(ConstraintViolationException e) {
return "Detect invalid parameter.";
}
}
À l'origine, il devrait être implémenté dans le gestionnaire d'exceptions global, mais ici, il est implémenté dans le contrôleur. En outre, la réponse d'erreur est implémentée de sorte qu'elle ne génère qu'un simple message d'erreur, donc implémentez-la selon vos besoins.
Réponse d'erreur après l'ajout de la gestion des exceptions
$ curl -D - http://localhost:8080/todos?sort=createdAt
HTTP/1.1 400
Content-Type: text/plain;charset=UTF-8
Content-Length: 25
Date: Sat, 17 Dec 2016 16:02:29 GMT
Connection: close
Detect invalid parameter.
Note:
Si l'application est bien conçue, je ne pense pas que l'utilisateur saisira manuellement les informations gérées par
Pageable
, donc je ne pense pas qu'il soit nécessaire de renvoyer une réponse d'erreur aimable lorsqu'une erreur est détectée. (Ce type d'erreur ne devrait pas se produire si vous utilisez la fonction fournie par l'application ...)
Cette fois, j'ai présenté comment valider l'interface Pageable
fournie par Spring Data. Je ne sais pas s'il est préférable d'utiliser la validation de méthode, mais je pense que cette méthode est recommandée pour le moment car elle peut être vérifiée de la même manière que la fonction de validation de Spring MVC.
Recommended Posts