web-dev-qa-db-fra.com

Comment valider les valeurs Spring MVC @PathVariable?

Pour une simple API JSON RESTful implémentée dans Spring MVC, puis-je utiliser la validation de bean (JSR-303) pour valider les variables de chemin d'accès transmises à la méthode de gestionnaire?

Par exemple:

 @RequestMapping(value = "/number/{customerNumber}")
 @ResponseBody
 public ResponseObject searchByNumber(@PathVariable("customerNumber") String customerNumber) {
 ...
 }

Ici, je dois valider la longueur des variables customerNumber à l'aide de la validation de bean. Est-ce possible avec Spring MVC v3.x.x? Si non, quelle est la meilleure approche pour ce type de validation?

Merci.

15
Grover

Spring ne prend pas en charge les paramètres annotés @javax.validation.Valid sur @PathVariable dans les méthodes de gestionnaire. Il y avait une demande d'amélioration, mais elle est toujours non résolue .

Votre meilleur choix est simplement de faire votre validation personnalisée dans le corps de la méthode de gestion ou d’envisager d’utiliser org.springframework.validation.annotation.Validated comme suggéré dans d’autres réponses.

26

Vous pouvez utiliser comme ceci: Utilisez org.springframework.validation.annotation.Validated pour valider RequestParam ou PathVariable.

 *
 * Variant of JSR-303's {@link javax.validation.Valid}, supporting the
 * specification of validation groups. Designed for convenient use with
 * Spring's JSR-303 support but not JSR-303 specific.
 *

step.1 init ValidationConfig

@Configuration
public class ValidationConfig {
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        return processor;
    }
}

step.2 Ajoutez @Validated à votre classe de gestionnaire de contrôleur, par exemple:

@RequestMapping(value = "poo/foo")
@Validated
public class FooController {
...
}

step.3 Ajoutez validators à votre méthode de gestionnaire:

   @RequestMapping(value = "{id}", method = RequestMethod.DELETE)
   public ResponseEntity<Foo> delete(
           @PathVariable("id") @Size(min = 1) @CustomerValidator int id) throws RestException {
        // do something
        return new ResponseEntity(HttpStatus.OK);
    }

dernière étape. Ajoutez le résolveur d'exceptions à votre contexte:

@Component
public class BindExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if (ex.getClass().equals(BindException.class)) {
            BindException exception = (BindException) ex;

            List<FieldError> fieldErrors = exception.getFieldErrors();
            return new ModelAndView(new MappingJackson2JsonView(), buildErrorModel(request, response, fieldErrors));
        }
    }
}
8
BeeNoisy

@PathVariable n'est pas destiné à être validé afin de renvoyer un message lisible à l'utilisateur. En principe, pathVariable ne doit jamais être invalide. Si pathVariable est invalide, la raison peut être:

  1. un bug a généré une mauvaise URL (un href dans jsp par exemple). Aucun @Valid n'est requis .__ et aucun message n'est nécessaire, corrigez simplement le code;
  2. "l'utilisateur" manipule l'URL . Encore une fois, aucun @Valid n'est nécessaire, aucun message significatif à l'utilisateur ne devrait être envoyé.

Dans les deux cas, laissez simplement une bulle d'exception jusqu'à ce qu'elle soit capturée par les Spring ExceptionHandlers habituels afin de générer un fichier Nice page d'erreur ou une réponse JSON significative indiquant l'erreur. Dans Pour obtenir ce résultat, vous pouvez effectuer des validations à l'aide d'éditeurs personnalisés.

Créer une classe CustomerNumber, éventuellement aussi immuable (implémenter une CharSequence n'est pas nécessaire, mais vous permet de l'utiliser essentiellement comme s'il s'agissait d'une String)

public class CustomerNumber implements CharSequence {

    private String customerNumber;

    public CustomerNumber(String customerNumber) {
        this.customerNumber = customerNumber;
    }

    @Override
    public String toString() {
        return customerNumber == null ? null : customerNumber.toString();
    }

    @Override
    public int length() {
        return customerNumber.length();
    }

    @Override
    public char charAt(int index) {
        return customerNumber.charAt(index);
    }

    @Override
    public CharSequence subSequence(int start, int end) {
        return customerNumber.subSequence(start, end);
    }

    @Override
    public boolean equals(Object obj) {
        return customerNumber.equals(obj);
    }

    @Override
    public int hashCode() {
        return customerNumber.hashCode();
    }
}

Créez un éditeur qui implémente votre logique de validation (dans ce cas, pas d'espaces blancs ni de longueur fixe, juste à titre d'exemple)

public class CustomerNumberEditor extends PropertyEditorSupport {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {

        if (StringUtils.hasText(text) && !StringUtils.containsWhitespace(text) && text.length() == YOUR_LENGTH) {
            setValue(new CustomerNumber(text));
        } else {
            throw new IllegalArgumentException();
            // you could also subclass and throw IllegalArgumentException
            // in order to manage a more detailed error message
        }
    }

    @Override
    public String getAsText() {
        return ((CustomerNumber) this.getValue()).toString();
    }
}

Enregistrer l'éditeur dans le contrôleur 

@InitBinder
public void initBinder(WebDataBinder binder) {

    binder.registerCustomEditor(CustomerNumber.class, new CustomerNumberEditor());
    // ... other editors
}

Changez la signature de votre méthode de contrôleur en acceptant CustomerNumber au lieu de String (quelle que soit votre ResponseObject ...)

@RequestMapping(value = "/number/{customerNumber}")
@ResponseBody
public ResponseObject searchByNumber(@PathVariable("customerNumber") CustomerNumber customerNumber) {
    ...
}
3
pdenti

La solution est simple:

@GetMapping(value = {"/", "/{hash:[a-fA-F0-9]{40}}"})
public String request(@PathVariable(value = "hash", required = false) String historyHash)
{
    // Accepted requests: either "/" or "/{40 character long hash}"
}

Et oui, les variables de chemin doivent être validées, comme toute entrée d'utilisateur.

1
Gábor Dani

La variable de chemin ne peut être liée à aucun bean de votre système. Que voulez-vous annoter avec les annotations JSR-303? Pour valider la variable de chemin, vous devez utiliser cette approche Problème lors de la validation de l’URL de @PathVariable sur spring 3 mvc

0
mvb13