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

Posted

技术标签:

【中文标题】PUT 和 POST 在未知属性上失败 Spring 不同的行为【英文标题】:PUT and POST fail on unknown properties Spring different behavior 【发布时间】:2016-04-05 09:36:33 【问题描述】:

我正在使用 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<Person, Long> 

我使用 Jackson 的反序列化功能来禁止 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 ,此项目中没有其他控制器或存储库。

【问题讨论】:

你是如何尝试这个解决方案的? http://***.com/a/14343479/3710490 嗯,现在即使有POST 请求,它也不会抛出异常,我猜它只适用于控制器,我必须添加一些其他 bean 才能使其适用于存储库。我试试看。 【参考方案1】:

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

PUT 在 Spring Data Rest 中的工作方式不同。有趣的部分在PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate处理。

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

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

这是我阅读代码的理解。我认为您可以争辩说这是一个错误。您可以尝试在 spring data rest`s jira 报告它 - https://jira.spring.io/browse/DATAREST。据我所知,没有办法自定义这种行为。

【讨论】:

感谢您的回复。所以 PUT 请求不会发生反序列化,这就解释了很多。我已经使用自定义 JsonDeserializer 制作了临时解决方案,并且它以某种方式工作,但我想我无论如何都会将 PUT/POST 行为差异报告为错误。 并且当数据库中没有具有给定ID的对象时,Spring会抛出正确的错误,因此在这种情况下会发生反序列化。也许这是设计使然,但在我看来它不是很直观。 我同意 - 它的行为不像预期的那样 - 但我们可以解释它,您可以确信错误不在您的实施中......【参考方案2】:

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

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

我做了快速示例如何获得所需的验证。

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

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

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

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

我确实将这个类复制到项目并修改了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

【讨论】:

感谢您的回答,我通过使用自定义反序列化器暂时解决了这个问题,我像您的解决方案一样强制转换为实体对象。我必须为每个实体类编写反序列化器,但我不需要那样弄乱类路径。 这只是一个可行的解决方案的想法,但您可以以更正确的方式做到这一点。您需要在提供 PersistentEntityResourceHandlerMethodArgumentResolver 版本的位置覆盖 RepositoryRestMvcConfiguration 配置。我尝试这样做,但遇到了 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 的错误配置。【参考方案3】:

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

@Entity
@JsonIgnoreProperties(ignoreUnknown=false)
public class Person
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String firstName;
    private String lastName;

    /* getters and setters */

【讨论】:

我已经注释了它但是它不起作用,Spring仍然忽略PUT请求中的未知字段。【参考方案4】:

您正在使用Jackson2ObjectMapperBuilder,它的属性DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 默认设置为disabled 值。

【讨论】:

我已使用builder.failOnUnknownProperties(true) 启用它。它并没有解释为什么POST 请求有效而PUT 无效。

以上是关于PUT 和 POST 在未知属性上失败 Spring 不同的行为的主要内容,如果未能解决你的问题,请参考以下文章

http请求中PUT GET POST区别?

未知usb设备(设备描述符请求失败)怎么解决?

使用 PUT 方法的 HTTPBody 中的数据失败,而它与 POST 一起使用?

301,302,303,307重定向区别

HTTP协议中PUT和POST使用上的区别

http put post请求区别