2015-12-31 8 views
17

Piszę aplikację Spring Boot, używając repozytoriów Spring Data Rest i chcę odmówić dostępu do zasobu, jeśli treść żądania zawiera JSON o nieznanych właściwościach. Definicja uproszczonej podmiotu i repozytorium:Niepowodzenie PUT i POST dla nieznanych właściwości Wiosenne różne zachowanie

@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> {} 

używam funkcji deserializacjia Jacksona aby uniemożliwić nieznanych właściwości w JSONs.

Podczas wysyłania wniosków POST wszystko działa zgodnie z oczekiwaniami. Kiedy używać poprawnych pól uzyskać prawidłową odpowiedź:

curl -i -x POST -H "Content-Type:application/json" -d '{"firstName": "Frodo", "lastName": "Baggins"}' http://localhost:8080/people 
{ 
    "firstName": "Frodo", 
    "lastName": "Baggins", 
    "_links": {...} 
} 

I kiedy wysłać JSON z nieznanych pól aplikacja zgłosi błąd: oczekiwano

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") 

metody PUT przy użyciu prawidłowego powraca JSON poprawną odpowiedź, jak również. Jednakże kiedy wysłać żądanie PUT z nieznanej dziedzinie spodziewam Wiosna wyrzucić błąd, ale zamiast tego, aktualizacje Wiosna obiektów w bazie danych i zwraca go:

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": {...} 
} 

Błąd jest generowany tylko wtedy, gdy nie ma żadnego obiektu w bazie danych ze względu 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") 

Czy to oczekiwane zachowanie lub błąd w Spring Data Rest? Jak mogę rzucić błąd, gdy JSON z nieznanymi właściwościami jest przekazywany do aplikacji bez względu na metodę żądania?

Mam powielana ten problem modyfikując http://spring.io/guides/gs/accessing-data-rest/, jedyna zmiana Zrobiłem to Jackson2ObjectMapperBuilder, żadne inne sterowniki lub repozytoria są w tym projekcie.

+0

Jak próbowałem tego rozwiązania? [http://stackoverflow.com/a/14343479/3710490](http://stackoverflow.com/a/14343479/3710490) – Valijon

+0

Hmm, teraz nie wyrzuca wyjątku nawet z prośbą o "POST", chyba to działa po wyjęciu z pudełka tylko z kontrolerami i muszę dodać kilka innych komponentów, aby działał z repozytoriami. Spróbuję tego. – Infinity

Odpowiedz

1

Używasz wartości Jackson2ObjectMapperBuilder, która ma domyślnie wartość DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES ustawioną na wartość disabled.

+0

Mam włączone to z 'builder.failOnUnknownProperties (true)'. I nie wyjaśnia, dlaczego działają żądania "POST", a "PUT" nie. – Infinity

1

można opisywać model z:

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

    private String firstName; 
    private String lastName; 

    /* getters and setters */ 
} 
+0

Mam adnotację, ale to nie działa, Spring nadal ignoruje nieznane pola w żądaniach PUT. – Infinity

14

myślę zachowanie obserwujesz jest zgodne z projektem. Po wydaniu polecenia POST tworzysz zasób, więc JSON jest deserializowany do typu jednostki, a Jackson wykonuje to zadanie.

A PUT działa inaczej w spoczynku danych sprężynowych. Ciekawa część jest obsługiwana w PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate.

Json jest odczytywany do JsonNode, jednostka jest odczytywana ze składnicy danych, a następnie w DomainObjectReader.doMerge implementacja iteruje po polach json. Stosuje json do encji i zapisuje go później w implementacji kontrolera. Odrzuca także wszystkie pola, które nie istnieją w trwałym obiekcie:

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

To jest moje zrozumienie po przeczytaniu kodu. Myślę, że możesz twierdzić, że to błąd. Możesz spróbować zgłosić to na wiosnę danych odpoczynku Jira - https://jira.spring.io/browse/DATAREST. O ile mi wiadomo, nie ma sposobu, aby dostosować to zachowanie.

+1

Dzięki za odpowiedź. Tak więc deserializacja nie występuje w przypadku żądań PUT, to wiele wyjaśnia. Zrobiłem tymczasowe rozwiązanie przy użyciu niestandardowego 'JsonDeserializer' i jakoś działa, ale myślę, że mimo to zgłoszę różnicę zachowania PUT/POST jako błąd. – Infinity

+1

A Spring rzuca poprawny błąd, gdy nie ma obiektu w bazie danych o podanym id, więc w tym przypadku wystąpi deserializacja. Być może jest to zgodne z projektem, ale moim zdaniem nie jest to zbyt intuicyjne. – Infinity

+0

Zgadzam się - nie działa zgodnie z oczekiwaniami - ale możemy to wyjaśnić i możesz być pewien, że błędu nie ma w twojej implementacji ... –

5

Kiedy tworzy nowy byt, konwertuje json bezpośrednio do obiektu java obiektu poprzez proces deserializacji, gdzie wymagane jest walidacja. Ale gdy aktualizuje istniejący podmiot, to konwertuje json na JsonNode, a następnie łączy się z istniejącą jednostką i zgodnie z oczekiwaniami nie następuje walidacja, ponieważ jest to funkcja do deserializacji json do obiektu java.

Aby obejść ten problem, można dodatkowo przekonwertować obiekt o nazwie JsonNode i będzie działał zgodnie z oczekiwaniami.

Zrobiłem szybki przykład, jak uzyskać wymaganą walidację.

iść do https://github.com/valery-barysok/gs-accessing-data-rest

Nie jest jasne rozwiązanie, ale można ją poprawić :)

Ten przykład zastąpić istniejącą klasę wiosna na ścieżce klasy org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver

Uwaga You must put ta klasa w ścieżce klas przed wersją oryginalną.

zrobiłem kopiowaniem przeszłość tej klasy do projektu i modyfikowane readPutForUpdate metody:

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); 
    } 
} 

i użyłem application.properties plik skonfigurować DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

+0

Dziękuję za odpowiedź, tymczasowo rozwiązałem ten problem, używając niestandardowych deserializatorów, wymuszam konwersja do obiektu encji jak w twoim rozwiązaniu. Muszę napisać deserializer dla każdej klasy jednostek, ale nie muszę w ten sposób mieszać z klasą ścieżek. – Infinity

+1

Ten pomysł na rozwiązanie, które działa, ale możesz zrobić to w bardziej odpowiedni sposób. Musisz zastąpić konfigurację 'RepositoryRestMvcConfiguration', w której podajesz swoją wersję' PersistentEntityResourceHandlerMethodArgumentResolver'. Próbowałem to zrobić, ale utknąłem z nieprawidłową konfiguracją 'DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES'. –