Как включить строгую проверку JSON / Jackson @RequestBody в Spring Boot REST API? - PullRequest
1 голос
/ 09 мая 2019

Как вывести ошибку, если в запросе JSON указаны дополнительные параметры?Например, «xxx» не является допустимым параметром или в объекте @RequestBody.

$ curl -X POST -H "Тип содержимого: application / json" -H "Авторизация: Носитель$ TOKEN "-d '{" apiKey ":"' $ APIKEY '"," email ":" name@example.com ", " xxx ":" yyy "}' localhost: 8080 / api/ v2 / stats

Я пытался добавить @Validated к интерфейсу, но это не помогло.

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

Я хотел бы включить «строгий» режим, чтобычто это даст ошибку, если в запросе существуют дополнительные, ложные параметры.Я не мог найти способ сделать это.Я нашел способы убедиться, что действительные параметры существуют, но нет способа убедиться, что нет дополнительных параметров.


public class ApiParams extends Keyable {

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

public abstract class Keyable {

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

Spring Boot 1.5.20

Ответы [ 4 ]

1 голос
/ 09 мая 2019

За кулисами Spring использует библиотеку Джексона для сериализации / десериализации POJO в JSON и наоборот. По умолчанию для ObjectMapper, используемого средой для выполнения этой задачи, FAIL_ON_UNKNOWN_PROPERTIES имеет значение false.

Вы можете включить эту функцию GLOBALLY , установив следующее значение конфигурации в application.properties.

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

Впоследствии, если вы хотите игнорировать неизвестные свойства для конкретного POJO, вы можете использовать аннотацию @JsonIgnoreProperties(ignoreUnknown=true) в этом классе POJO.

Тем не менее, впереди много ручной работы. Технически, игнорирование этих неожиданных данных не нарушает никаких принципов разработки программного обеспечения. Могут быть сценарии, когда перед вашим @Controller находится фильтр или сервлет, выполняющий дополнительные действия, о которых вы не знаете, для которых требуются эти дополнительные данные. Стоит ли усилий?

0 голосов
/ 09 мая 2019

Вы можете попытаться предоставить пользовательскую реализацию для класса 'MappingJackson2HttpMessageConverter' для этого преобразования сообщения.

    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;
        }
    }

Теперь только нам нужно зарегистрировать Custom MessageConverter в контексте пружины. В классе конфигурации. Ниже приведен код

@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);
}

Надеюсь, это поможет ..

0 голосов
/ 09 мая 2019

Я нашел решение здесь: https://stackoverflow.com/a/47984837/148844

Я добавил это в свое приложение.

@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;
    }

@JsonIgnoreProperties не был необходим.Теперь он возвращает ошибку типа

{"status": "BAD_REQUEST", "timestamp": "2019-05-09T05: 30: 02Z", "errorCode": 20, "errorMessage":«Неправильный запрос JSON», «debugMessage»: «Ошибка разбора JSON: нераспознанное поле \« xxx \ »(класс com.example.ApiParams), не помечено как игнорируемое; вложенное исключение - com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Нераспознанное поле \ "xxx \" (класс com.example.ApiParams), не помеченное как игнорируемое (2 известных свойства: \ "email \", \ "apiKey \"]) \ n в [Source: java.io.PushbackInputStream@ 6bec9691; строка: 1, столбец: 113] (через цепочку ссылок: com.example.ApiParams [\ "xxx \"]) "}

(у меня есть класс @ControllerAdviceResponseEntityExceptionHandler.)

0 голосов
/ 09 мая 2019

Я знаю, что это не лучшее решение, но, тем не менее, я публикую его.

Вы можете реализовать Interceptor для URL вашего контроллера. В методе preHandle вашего перехватчика вы сможете получить HttpServletRequest объект, из которого вы можете получить все параметры запроса. В этом методе вы можете написать код для строгой проверки параметров запроса и создать исключение для недопустимых параметров, присутствующих в вашем запросе.

Вы также можете написать код проверки в вашем классе контроллера, получив объект HttpRequest в вашем методе Controller, но было бы хорошо хранить логику контроллера и логику проверки в отдельном пространстве.

Перехватчик:

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;
    }

}

Вам также следует посмотреть ответы, приведенные в Как проверить параметры несвязанного запроса в методе контроллера Spring MVC? .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...