可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
How do I throw an error if extra parameters are specified in the JSON request? For example, "xxx" is not a valid parameter or in the @RequestBody
object.
$ 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
I tried adding @Validated
to the interface, but it didn't help.
@RequestMapping(value = "/api/v2/stats", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<DataResponse> stats(Principal principal, @Validated @RequestBody ApiParams apiParams) throws ApiException;
I would like to enable a 'strict' mode so that it will give an error if extra, spurious parameters exist in the request. I could find no way to do this. I found ways to ensure the valid parameters do exist, but no way to ensure there are not extra parameters.
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
回答1:
Behind the scene, Spring uses the Jackson library to serialize/deserialize POJO to JSON and vice versa. By default, the ObjectMapper
that the framework uses to perform this task has its FAIL_ON_UNKNOWN_PROPERTIES
set to false
.
You can turn this feature on GLOBALLY by setting the following config value in application.properties
.
spring.jackson.deserialization.fail-on-unknown-properties=true
Subsequently, if you want to ignore unknown properties for specific POJO, you can use the annotation @JsonIgnoreProperties(ignoreUnknown=true)
in that POJO class.
Still, this is a lot of manual work going forward. Technically, ignoring those unexpected data doesn't violate any software development principles. There might be scenarios where there's a filter or servlet sitting in front of your @Controller
doing additional stuff that you're not aware of which requires those extra data. Does it worth the effort?
回答2:
You could try providing a custom implementation for 'MappingJackson2HttpMessageConverter ' class for this message conversion.
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;
}
}
Now only we need to register the Custom MessageConverter with the spring context.In the configuration class.Below is the code
@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);
}
Hope that helps ..
回答3:
I know this is not the best solution but still, I'm posting it.
You can implement Interceptor
for your controller URL. In preHandle
method of your interceptor, you will be able to get HttpServletRequest
object from which you can get all the request parameters. In this method, you can write code to write strict validation for request parameters and throw an exception for invalid parameters present in your request.
You can write the validation code in your controller class as well by getting HttpRequest
object in your Controller method but it would be good to keep your controller logic and validation logic in separate space.
Interceptor :
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;
}
}
You should also look at the answers given in How to check for unbound request parameters in a Spring MVC controller method? as well.
回答4:
I found a solution here: https://stackoverflow.com/a/47984837/148844
I added this to my Application.
@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;
}
The @JsonIgnoreProperties
was not needed. Now it returns an error like
{"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\"])"}
(I happen to have a @ControllerAdvice
class ResponseEntityExceptionHandler
.)