如何在Spring Boot REST API中启用对JSON / Jackson @RequestBody的严格验证?

时间:2019-05-09 04:35:32

标签: java spring rest spring-boot jackson

如果在JSON请求中指定了额外的参数,如何引发错误?例如,“ xxx”不是有效的参数,也不是@RequestBody对象中的参数。

  

$ curl -X POST -H“内容类型:application / json” -H“授权:Bearer $ 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 个答案:

答案 0 :(得分:1)

在后台,Spring使用Jackson库将POJO序列化/反序列化为JSON,反之亦然。默认情况下,框架用于执行此任务的ObjectMapper的{​​{1}}设置为FAIL_ON_UNKNOWN_PROPERTIES

您可以通过在false中设置以下配置值来全局启用此功能。

application.properties

随后,如果要忽略特定POJO的未知属性,则可以在该POJO类中使用注释spring.jackson.deserialization.fail-on-unknown-properties=true

仍然,这是大量的手工工作。从技术上讲,忽略那些意外的数据不会违反任何软件开发原则。在某些情况下,@JsonIgnoreProperties(ignoreUnknown=true)前面有一个过滤器或servlet,它们在做一些其他的事情,而您不知道哪些需要这些额外的数据。值得付出努力吗?

答案 1 :(得分:0)

我知道这不是最好的解决方案,但仍在发布中。

您可以为您的控制器URL实现Interceptor。在拦截器的preHandle方法中,您将可以获取HttpServletRequest对象,从中可以获取所有请求参数。通过这种方法,您可以编写代码以对请求参数进行严格的验证,并为请求中存在的无效参数引发异常。

您也可以通过在Controller方法中获取HttpRequest对象来在控制器类中编写验证代码,但是最好将控制器逻辑和验证逻辑放在单独的空间中。

拦截器:

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

}

您还应该查看How to check for unbound request parameters in a Spring MVC controller method?中给出的答案。

答案 2 :(得分:0)

您可以尝试为“ 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;
        }
    }

现在只需要在spring上下文中注册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);
}

希望有帮助..

答案 3 :(得分:0)

我在这里找到了解决方案: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。现在它返回类似

的错误
  

{“状态”:“ BAD_REQUEST”,“时间戳”:“ 2019-05-09T05:30:02Z”,“错误代码”:20,“错误消息”:“格式错误的JSON请求”,“调试消息”:“ JSON解析错误:无法识别的字段\“ xxx \”(类com.example.ApiParams),未标记为可忽略;嵌套的异常是com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:无法识别的字段\“ xxx \”(类com。 example.ApiParams),未标记为可忽略(2个已知属性:\“ email \”,\“ apiKey \”])\ n位于[来源:java.io.PushbackInputStream@6bec9691;行:1,列:113](通过参考链:com.example.ApiParams [\“ xxx \”])“}

(我碰巧有一个@ControllerAdviceResponseEntityExceptionHandler。)