3
votes

Comment activer la validation stricte de JSON / Jackson @RequestBody dans l'API REST Spring Boot?

Comment puis-je générer une erreur si des paramètres supplémentaires sont spécifiés dans la requête JSON? Par exemple, "xxx" n'est pas un paramètre valide ou dans l'objet @RequestBody .

$ curl -X POST -H "Content-Type: application / json" -H "Autorisation: Bearer $ TOKEN" -d '{"apiKey": "' $ APIKEY '", "email": "nom @ example.com ", " xxx ":" yyy "} 'localhost: 8080 / api / v2 / stats

J'ai essayé d'ajouter @Validated à l'interface, mais cela n'a pas aidé.

public abstract class Keyable {

    @ApiModelProperty(notes = "API key", required = true)
    @NotNull
    String apiKey;

Je voudrais activer un mode 'strict' pour qu'il donne une erreur si des paramètres supplémentaires et parasites existent dans la requête. Je n'ai trouvé aucun moyen de le faire. J'ai trouvé des moyens de m'assurer que les paramètres valides existent, mais aucun moyen de garantir qu'il n'y a pas de paramètres supplémentaires.


public class ApiParams extends Keyable {

    @ApiModelProperty(notes = "email of user", required = true)
    String email;

@RequestMapping(value = "/api/v2/stats", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<DataResponse> stats(Principal principal, @Validated @RequestBody ApiParams apiParams) throws ApiException;

Spring Boot 1.5.20


3 commentaires

Votre classe ApiParams a-t-elle une annotation JsonIgnoreProperties?


essayez de le définir sur false


Non, ce n'est pas le cas. L'ajout de @JsonIgnoreProperties (ignoreUnknown = false) à la classe ne semble pas aider.


4 Réponses :


0
votes

Je sais que ce n'est pas la meilleure solution, mais je la poste quand même.

Vous pouvez implémenter Interceptor pour l'URL de votre contrôleur. Dans la méthode preHandle de votre intercepteur, vous pourrez obtenir l'objet HttpServletRequest à partir duquel vous pourrez obtenir tous les paramètres de la requête. Dans cette méthode, vous pouvez écrire du code pour écrire une validation stricte des paramètres de requête et lancer une exception pour les paramètres non valides présents dans votre requête.

Vous pouvez également écrire le code de validation dans votre classe de contrôleur en obtenant Objet HttpRequest dans votre méthode Controller, mais il serait bon de conserver la logique de votre contrôleur et la logique de validation dans un espace séparé.

Intercepteur:

public class MyInterceptor extends HandlerInterceptorAdapter {

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
    throws Exception {
    // TODO Auto-generated method stub

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    throws Exception {
    // TODO Auto-generated method stub

    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HandlerMethod handlerMethod = (HandlerMethod) handler;

        Map<String, String[]> parameters = request.getParameterMap();
        //Write your validation code

        return true;
    }

}

Vous devriez également regarder les réponses données dans Comment vérifier les paramètres de requête non liés dans une méthode de contrôleur Spring MVC? également.


0 commentaires

0
votes

Vous pouvez essayer de fournir une implémentation personnalisée pour la classe 'MappingJackson2HttpMessageConverter' pour cette conversion de message.

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    CustomMappingJackson2HttpMessageConverter jsonConverter =
                    CustomMappingJackson2HttpMessageConverter();
    List<MediaType> mediaTypeList = new ArrayList<MediaType>();
    mediaTypeList.add(MediaType.APPLICATION_JSON);
    jsonConverter.setSupportedMediaTypes(mediaTypeList);
    converters.add(jsonConverter);
    super.configureMessageConverters(converters);
}

Il ne nous reste plus qu'à enregistrer le Custom MessageConverter avec le contexte Spring.Dans la classe de configuration. Ci-dessous le code

    public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

        private static final Logger logger =

        private ObjectMapper objectMapper;

        private boolean prefixJson = false;

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#setPrefixJson(boolean)
         */
        @Override
        public void setPrefixJson(boolean prefixJson) {
            this.prefixJson = prefixJson;
            super.setPrefixJson(prefixJson);
        }


        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#read(java.lang.reflect.Type,
         * java.lang.Class, org.springframework.http.HttpInputMessage)
         */
        @Override
        public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
                        throws IOException, HttpMessageNotReadableException {
            objectMapper = new ObjectMapper();

/* HERE THIS IS THE PROPERTY YOU ARE INTERESTED IN */
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);


            objectMapper.configure(DeserializationFeature.ACCEPT_FLOAT_AS_INT, false);
            objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
            objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, true);
            objectMapper.configure(JsonParser.Feature.STRICT_DUPLICATE_DETECTION, true);

            InputStream istream = inputMessage.getBody();
            String responseString = IOUtils.toString(istream);
            try {
                return objectMapper.readValue(responseString, OperatorTokenDefinition.class);
            } catch (UnrecognizedPropertyException ex) {
               throw new YourCustomExceptionClass();
            } catch (InvalidFormatException ex) { 
               throw new YourCustomExceptionClass();
            } catch (IgnoredPropertyException ex) {
                throw new YourCustomExceptionClass();
            } catch (JsonMappingException ex) {
                throw new YourCustomExceptionClass();
            } catch (JsonParseException ex) {
                logger.error("Could not read JSON JsonParseException:{}", ex);
                throw new YourCustomExceptionClass();
            }
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#supports(java.lang.Class)
         */
        @Override
        protected boolean supports(Class<?> arg0) {
            return true;
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal(java.lang.Object,
         * org.springframework.http.HttpOutputMessage)
         */
        @Override
        protected void writeInternal(Object arg0, HttpOutputMessage outputMessage)
                        throws IOException, HttpMessageNotWritableException {
            objectMapper = new ObjectMapper();
            String json = this.objectMapper.writeValueAsString(arg0);
            outputMessage.getBody().write(json.getBytes(Charset.defaultCharset()));
        }

        /**
         * @return
         */
        private ResourceBundleMessageSource messageSource() {
            ResourceBundleMessageSource source = new ResourceBundleMessageSource();
            source.setBasename("messages");
            source.setUseCodeAsDefaultMessage(true);
            return source;
        }
    }

J'espère que cela vous aidera ..


0 commentaires

0
votes

J'ai trouvé une solution ici: https://stackoverflow.com/a/47984837/148844

I ajouté ceci à mon application.

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    @Bean
    @Primary
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
        return objectMapper;
    }

Le @JsonIgnoreProperties n'était pas nécessaire. Maintenant, il renvoie une erreur comme

{"status": "BAD_REQUEST", "timestamp": "2019-05-09T05: 30: 02Z", "errorCode": 20, "errorMessage": "Requête JSON incorrecte", "debugMessage": "JSON erreur d'analyse: champ non reconnu \ "xxx \" (classe com.example.ApiParams), non marqué comme ignorable; l'exception imbriquée est com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: champ non reconnu \ "xxx \" (classe com. example.ApiParams), non marqué comme ignorable (2 propriétés connues: \ "email \", \ "apiKey \"]) \ n à [Source: java.io.PushbackInputStream@6bec9691; ligne: 1, colonne: 113] ( via la chaîne de référence: com.example.ApiParams [\ "xxx \"]) "}

(Il se trouve que j'ai une classe @ControllerAdvice ResponseEntityExceptionHandler.)


0 commentaires

4
votes

En coulisses, Spring utilise la bibliothèque Jackson pour sérialiser / désérialiser POJO en JSON et vice versa. Par défaut, le ObjectMapper que le framework utilise pour effectuer cette tâche a son FAIL_ON_UNKNOWN_PROPERTIES défini sur false .

Vous pouvez activer cette fonctionnalité GLOBALEMENT en définissant la valeur de configuration suivante dans application.properties.

spring:
  jackson:
    deserialization:
      fail-on-unknown-properties: true

Ou si vous utilisez le format YAML, ajoutez ce qui suit à votre fichier application.yaml (ou .yml )):

spring.jackson.deserialization.fail-on-unknown-properties=true

Par la suite, si vous souhaitez ignorer l'inconnu propriétés pour un POJO spécifique, vous pouvez utiliser l'annotation @JsonIgnoreProperties (ignoreUnknown = true) dans cette classe POJO.


Néanmoins, cela pourrait signifier beaucoup de travail manuel vers l'avant. Techniquement, ignorer ces données inattendues ne viole aucun principe de développement logiciel. Il peut y avoir des scénarios où il y a un filtre ou un servlet assis devant votre @Controller faisant des choses supplémentaires dont vous n'êtes pas au courant et qui nécessitent ces données supplémentaires. Cela semble-t-il valoir la peine?


1 commentaires

Peut-être pas si utile pour passer à la production, mais pour le développement, cela peut être incroyablement utile. Merci pour cette réponse.