我正在使用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
,其他控制器或存储库都没有项目
答案 0 :(得分:14)
我认为你观察到的行为是设计的。发布POST时,您正在创建资源,以便将JSON反序列化为您的实体类型,并且Jackson正在执行此任务。
PUT在弹簧数据休息中的工作方式不同。有趣的部分在dataGridView1.DefaultCellStyle.Padding = new Padding(0, 0, 0, 0);
dataGridView1.Columns["qr Code"].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
dataGridView1.Columns["QR Code"].AutoSizeMode = DataGridViewAutoSizeColumnMode.DisplayedCells;
处理。
将json读入PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate
,从数据存储中读取实体,然后在JsonNode
中实现迭代json字段。它将json应用于实体并稍后将其保存在控制器实现中。它还会丢弃持久化实体中不存在的所有字段:
DomainObjectReader.doMerge
这是我阅读代码时的理解。我想你可以说这是一个错误。您可以尝试在spring data rest jira - https://jira.spring.io/browse/DATAREST报告它。据我所知,无法自定义此行为。
答案 1 :(得分:5)
当它创建新实体时,它通过反序列化过程将json直接转换为java实体对象,其中涉及所需的验证。但是当它更新现有实体时,它会将json转换为JsonNode
,然后与现有实体合并,并且正如预期的那样,不会发生验证,因为它是json反序列化到java对象的功能。
作为解决方法,您还可以将JsonNode
转换为实体对象,它将按预期工作。
我做了快速举例说明如何获得所需的验证。
转到https://github.com/valery-barysok/gs-accessing-data-rest
目前尚不清楚解决方案,但您可以改进它:)
此示例覆盖类路径org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver
注意 必须在原始版本之前将此类放在classpath上。
我将此类复制到项目并修改了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
答案 2 :(得分:1)
您正在使用Jackson2ObjectMapperBuilder
,其默认情况下属性DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
设置为disabled
。
答案 3 :(得分:1)
您可以使用以下方式注释您的模型:
@Entity
@JsonIgnoreProperties(ignoreUnknown=false)
public class Person{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
/* getters and setters */
}