PUT 和 POST 在未知属性上失败 弹簧不同行为

2022-09-02 13:14:17

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


答案 1

我认为你观察到的行为是有意为之的。发出 POST 时,您正在创建资源,以便将 JSON 定向化为实体类型,并且 Jackson 正在执行此任务。

PUT在春季数据休息中的工作方式不同。有趣的部分在 中处理。PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate

将 json 读入 到 中,从数据存储中读取实体,然后在实现中循环访问 json 字段。它将 json 应用于实体,并在稍后将其保存在控制器实现中。它还会丢弃持久性实体中不存在的所有字段:JsonNodeDomainObjectReader.doMerge

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

这是我从阅读代码中了解到的。我想你可以说这是一个错误。您可以尝试在spring data rest的jira上报告它 - https://jira.spring.io/browse/DATAREST。据我所知,没有办法定制这种行为。


答案 2

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

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

我做了一个快速示例,说明如何获得所需的验证。

转到 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.propertiesDeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES


推荐