如何在 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\"])"
(我碰巧有一个@ControllerAdvice
类ResponseEntityExceptionHandler
。)
【讨论】:
【参考方案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 中添加自定义属性