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

Posted

技术标签:

【中文标题】如何在 Spring Boot REST API 中启用对 JSON / Jackson @RequestBody 的严格验证?【英文标题】:How do I enable strict validation of JSON / Jackson @RequestBody in Spring Boot REST API? 【发布时间】:2019-09-26 20:49:04 【问题描述】:

如果在 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 注释? 尝试将其设置为 false 不,它没有。在课程中添加@JsonIgnoreProperties(ignoreUnknown=false) 似乎没有帮助。 【参考方案1】:

在幕后,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)


不过,这可能意味着需要大量手动工作。从技术上讲,忽略那些意外数据并不违反任何软件开发原则。在某些情况下,您的@Controller 前面有一个过滤器或 servlet,正在执行您不知道的其他事情,这些事情需要这些额外的数据。看起来值得付出努力吗?

【讨论】:

对于生产来说可能不是那么有用,但对于开发来说这可能非常有用。感谢您的回答。【参考方案2】:

我在这里找到了解决方案:https://***.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 类) example.ApiParams),未标记为可忽略(2 个已知属性:\"email\"、\"apiKey\"])\n 在 [Source: java.io.PushbackInputStream@6bec9691; line: 1, column: 113] (通过引用链:com.example.ApiParams[\"xxx\"])"

(我碰巧有一个@ControllerAdviceResponseEntityExceptionHandler。)

【讨论】:

【参考方案3】:

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

您可以为控制器 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? 中给出的答案。

【讨论】:

【参考方案4】:

您可以尝试为此消息转换提供“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);

希望对您有所帮助..

【讨论】:

以上是关于如何在 Spring Boot REST API 中启用对 JSON / Jackson @RequestBody 的严格验证?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot JWT - 如何实现刷新令牌和注销 REST-API

如何在 Spring Boot REST API 中格式化返回的 json?

在 Spring-boot 中成功登录后如何限制 POST Rest Api 以供公共访问

如何在 Spring Boot Rest api 响应的 ResponseEntity 中添加自定义属性

如何在 Spring Boot Rest API 中以 XML 形式返回对象列表

如何在 Spring Boot Rest api 中创建安全登录控制器