PUT和POST在未知属性上失败Spring不同的行为

我正在使用Spring Data Rest存储库编写Spring Boot应用程序,如果请求主体包含具有未知属性的JSON,我想拒绝访问资源。 简化实体和存储库的定义:

@Entity public class Person{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String firstName; private String lastName; /* getters and setters */ } @RepositoryRestResource(collectionResourceRel = "people", path = "people") public interface PersonRepository extends CrudRepository {} 

我使用Jackson的反序列化function来禁止JSON中的未知属性。

 @Bean public Jackson2ObjectMapperBuilder objectMapperBuilder(){ Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.failOnUnknownProperties(true); return builder; } 

当我发送POST请求时,一切都按预期工作。 当我使用有效字段时,我得到正确答案:

 curl -i -x POST -H "Content-Type:application/json" -d '{"firstName": "Frodo", "lastName": "Baggins"}' http://localhost:8080/people { "firstName": "Frodo", "lastName": "Baggins", "_links": {...} } 

当我发送带有未知字段的JSON时,应用程序会抛出预期的错误:

 curl -i -x POST -H "Content-Type:application/json" -d '{"unknown": "POST value", "firstName": "Frodo", "lastName": "Baggins"}' http://localhost:8080/people com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "unknown" (class Person), not marked as ignorable (2 known properties: "lastName", "firstName") 

使用有效JSON时的PUT方法也会返回正确的响应。 但是,当我发送带有未知字段的PUT请求时,我希望Spring抛出错误,而不是那样,Spring更新数据库中的对象并返回它:

 curl -i -x PUT -H "Content-Type:application/json" -d '{"unknown": "PUT value", "firstName": "Bilbo", "lastName": "Baggins"}' http://localhost:8080/people/1 { "firstName": "Bilbo", "lastName": "Baggins", "_links": {...} } 

仅当数据库中没有具有给定id的对象时才会引发错误:

 curl -i -x PUT -H "Content-Type:application/json" -d '{"unknown": "PUT value", "firstName": "Gandalf", "lastName": "Baggins"}' http://localhost:8080/people/100 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "unknown" (class Person), not marked as ignorable (2 known properties: "lastName", "firstName") 

它是Spring Data Rest中的预期行为还是错误? 无论请求方法是什么,当具有未知属性的JSON传递给应用程序时,如何抛出错误?

我通过修改http://spring.io/guides/gs/accessing-data-rest/重现了这种行为 ,我唯一的改变就是Jackson2ObjectMapperBuilder ,这个项目中没有其他控制器或存储库。

我认为你观察到的行为是设计的。 发出POST时,您正在创建资源,以便将JSON反序列化为您的实体类型,并且Jackson正在执行此任务。

PUT在弹簧数据rest中的工作方式不同。 有趣的部分在PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate处理。

将json读入JsonNode ,从数据存储中读取实体,然后在DomainObjectReader.doMerge中实现迭代json字段。 它将json应用于实体并稍后将其保存在控制器实现中。 它还会丢弃持久化实体中不存在的所有字段:

 if (!mappedProperties.hasPersistentPropertyForField(fieldName)) { i.remove(); continue; } 

这是我阅读代码时的理解。 我想你可以说这是一个错误。 您可以尝试在春季数据rest时报告它jira – https://jira.spring.io/browse/DATAREST 。 据我所知,没有办法自定义此行为。

当它创建新实体时,它通过反序列化过程将json直接转换为java实体对象,其中涉及所需的validation。 但是当它更新现有实体时,它会将json转换为JsonNode ,然后与现有实体合并,并且正如预期的那样,不会发生validation,因为它是json反序列化到java对象的function。

作为解决方法,您还可以将JsonNode转换为实体对象,它将按预期工作。

我做了快速示例,了解如何获得所需的validation。

转到https://github.com/valery-barysok/gs-accessing-data-rest

这不是明确的解决方案,但你可以改善它:)

此示例覆盖类路径上的现有spring类org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver

注意必须在原始版本之前将此类放在类路径上。

我复制了这个类来进行项目和修改的readPutForUpdate方法:

 private Object readPutForUpdate(IncomingRequest request, ObjectMapper mapper, Object existingObject, RootResourceInformation information) { try { JsonPatchHandler handler = new JsonPatchHandler(mapper, reader); JsonNode jsonNode = mapper.readTree(request.getBody()); // Here we have required validation mapper.treeToValue(jsonNode, information.getDomainType()); return handler.applyPut((ObjectNode) jsonNode, existingObject); } catch (Exception o_O) { throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, existingObject.getClass()), o_O); } } 

我使用application.properties文件来配置DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

您正在使用Jackson2ObjectMapperBuilder ,默认情况下将DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES属性设置为disabled值。

你可以用以下方法注释你的模型:

 @Entity @JsonIgnoreProperties(ignoreUnknown=false) public class Person{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String firstName; private String lastName; /* getters and setters */ }