PUT и POST сбой при неизвестных свойствах Spring различное поведение
Я пишу Spring приложение для загрузки с помощью репозиториев Spring Data Rest и я хочу запретить доступ к ресурсу, если тело запроса содержит 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> {}
Я использую функцию десериализации Джексона, чтобы запретить неизвестные свойства в 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")
Метод PUT при использовании действительного JSON также возвращает правильный ответ. Однако, когда я отправляю запрос 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": {...}
}
Ошибка возникает только тогда, когда в базе данных с данным идентификатором нет объекта:
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 десериализуется в ваш тип сущности, и Джексон выполняет эту задачу.
PUT работает по-разному в режиме хранения данных spring. Интересная часть обрабатывается в PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate
.
json считывается в JsonNode
, объект считывается из хранилища данных, а затем в DomainObjectReader.doMerge
реализация выполняет итерацию над полями json. Он применяет json к сущности и сохраняет его позже в реализации контроллера. Он также отбрасывает все поля, которые не существуют в постоянном объекте:
if (!mappedProperties.hasPersistentPropertyForField(fieldName)) {
i.remove();
continue;
}
Это мое понимание, прочитав код. Я думаю, вы можете утверждать, что это ошибка. Вы можете попытаться сообщить об этом в spring data rest `jira - https://jira.spring.io/browse/DATAREST. Насколько я знаю, нет способа настроить это поведение.
Ответ 2
Когда он создает новый объект, он преобразует json непосредственно в объект сущности java через процесс десериализации, где задействована требуемая проверка. Но когда он обновляет существующий объект, он преобразует json в JsonNode
, а затем сливается с существующим сущностью и, как ожидается, проверка не происходит, потому что это функция десериализации json для java-объекта.
В качестве обходного пути вы можете дополнительно преобразовать объект JsonNode
в объект объекта, и он будет работать так, как вы ожидаете.
Я сделал быстрый пример, как получить требуемую проверку.
перейти к https://github.com/valery-barysok/gs-accessing-data-rest
Это непонятное решение, но вы можете его улучшить:)
В этом примере переопределить существующий класс spring в classpath 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
Ответ 3
Вы используете Jackson2ObjectMapperBuilder
, у которого свойство DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
установлено по значению disabled
по умолчанию.
Ответ 4
вы можете аннотировать вашу модель с помощью:
@Entity
@JsonIgnoreProperties(ignoreUnknown=false)
public class Person{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
/* getters and setters */
}