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

6
如何在JSON请求中指定了额外参数时抛出错误?例如,“xxx”不是有效的参数或在@RequestBody对象中。

$ curl -X POST -H "Content-Type: application/json" -H "Authorization: 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


你的 ApiParams 类是否有 JsonIgnoreProperties 注解? - sidgate
尝试将其设置为false。 - sidgate
1
不,它不会。将 @JsonIgnoreProperties(ignoreUnknown=false) 添加到类中似乎没有帮助。 - Chloe
4个回答

12

在幕后,Spring使用Jackson库将POJO序列化/反序列化为JSON并反之亦然。框架默认使用的ObjectMapper执行此任务时,其FAIL_ON_UNKNOWN_PROPERTIES设置为false

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

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

如果使用YAML格式,则将以下内容添加到您的application.yaml(或.yml)文件中:

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

接下来,如果你想忽略特定POJO的未知属性,你可以在该POJO类中使用注解@JsonIgnoreProperties(ignoreUnknown=true)


但是,这可能会导致将来需要大量手动工作。从技术上讲,忽略这些意外的数据并不违反任何软件开发原则。有些情况下,可能有一个过滤器或者Servlet坐在你的@Controller前面做额外的事情,需要这些额外的数据,你可能没有意识到。这值得付出努力吗?


3
也许对于投入生产并不那么有用,但对于开发来说,这非常有用。感谢您的回答。 - Per Lundberg

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

}

你还应该查看如何在Spring MVC控制器方法中检查未绑定的请求参数?中给出的答案。


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上下文中注册自定义的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

我在这里找到了一个解决方案:https://dev59.com/h53ha4cB1Zd3GeqPSlNC#47984837

我将其添加到我的应用程序中。

@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":"Malformed JSON request","debugMessage":"JSON parse error: Unrecognized field \"xxx\" (class com.example.ApiParams), not marked as ignorable; nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field \"xxx\" (class com.example.ApiParams), not marked as ignorable (2 known properties: \"email\", \"apiKey\"])\n at [Source: java.io.PushbackInputStream@6bec9691; line: 1, column: 113] (through reference chain: com.example.ApiParams[\"xxx\"])"}

(我碰巧有一个 @ControllerAdviceResponseEntityExceptionHandler。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接