Подтвердить что ты не робот

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, никаких других контроллеры или репозитории находятся в этом проекте.

4b9b3361

Ответ 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 */
}