2015-12-31 3 views
17

Sto scrivendo l'applicazione Spring Boot utilizzando i repository Spring Data Rest e desidero negare l'accesso alla risorsa se il corpo della richiesta contiene JSON con proprietà sconosciute. Definizione di entità semplificata e repository:Errore PUT e POST su proprietà sconosciute Comportamento diverso molla

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

io uso funzione di deserializzazione di Jackson per non consentire proprietà sconosciute in JSONs.

@Bean 
public Jackson2ObjectMapperBuilder objectMapperBuilder(){ 
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); 
    builder.failOnUnknownProperties(true); 
    return builder; 
} 

Quando invio richieste POST, tutto funziona come previsto. Quando uso campi validi ottengo risposta corretta:

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

E quando invio JSON con campi sconosciuti applicazione genera aspettavo di errore:

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

metodo PUT quando si utilizzano i rendimenti JSON validi risposta corretta pure. Tuttavia quando invio richiesta PUT con campo sconosciuto mi aspetto di primavera per lanciare l'errore, ma invece di che, gli aggiornamenti di primavera oggetto nel database e lo restituisce:

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

L'errore viene generato solo quando non ci sono oggetti nel database con data 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") 

e 'un comportamento previsto o un bug in primavera dati Rest? Come posso generare un errore quando JSON con proprietà sconosciute viene passato all'applicazione indipendentemente dal metodo di richiesta?

Ho riprodotto questo comportamento modificando http://spring.io/guides/gs/accessing-data-rest/, l'unico cambiamento che ho fatto è Jackson2ObjectMapperBuilder, nessun altro controller o repository sono in questo progetto.

+0

Come si è tentato questa soluzione? [http://stackoverflow.com/a/14343479/3710490](http://stackoverflow.com/a/14343479/3710490) – Valijon

+0

Hmm, ora non genera eccezioni anche con le richieste 'POST', immagino che funzioni out of the box solo con i controller e devo aggiungere altri bean per farlo funzionare con i repository. Lo proverò. – Infinity

risposta

1

Si sta utilizzando Jackson2ObjectMapperBuilder, che ha la proprietà DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES impostata sul valore disabled per impostazione predefinita.

+0

L'ho abilitato con 'builder.failOnUnknownProperties (true)'. E non spiega perché le richieste 'POST' funzionano e' PUT' no. – Infinity

1

è possibile annotare il modello con:

@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

L'ho annotato ma non funziona, Spring ignora ancora i campi sconosciuti nelle richieste PUT. – Infinity

14

Credo che il comportamento si sta osservando è di progettazione. Quando viene emesso un POST, si crea la risorsa in modo che il JSON venga deserializzato nel tipo di entità e Jackson esegua questa attività.

Un PUT funziona diversamente nel periodo di riposo dei dati di primavera. La parte interessante è gestita in PersistentEntityResourceHandlerMethodArgumentResolver.readPutForUpdate.

Il json viene letto in un JsonNode, l'entità viene letta dall'archivio dati e quindi in DomainObjectReader.doMerge l'iterazione viene eseguita sui campi json. Applica il json all'entità e lo salva successivamente nell'implementazione del controller. Elimina inoltre tutti i campi che non esistono nell'entità persistente:

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

Questa è la mia comprensione dalla lettura del codice. Penso che potresti sostenere che questo è un bug. Potresti provare a segnalarlo a jira di Spring Data Rest - https://jira.spring.io/browse/DATAREST. Per quanto ne so non c'è modo di personalizzare questo comportamento.

+1

Grazie per la risposta. Quindi la deserializzazione non si verifica con le richieste PUT, questo spiega molto. Ho realizzato una soluzione temporanea usando 'JsonDeserializer 'personalizzato e in qualche modo funziona, ma suppongo che segnalerò comunque la differenza di comportamento PUT/POST come un bug. – Infinity

+1

E Spring genera l'errore corretto quando non c'è alcun oggetto nel database con un dato ID, quindi in questo caso si verifica la deserializzazione. Forse è di design, ma a mio parere non è molto intuitivo. – Infinity

+0

Sono d'accordo - non si comporta come previsto - ma possiamo spiegarlo e puoi essere sicuro che l'errore non è nella tua implementazione ... –

5

Quando crea una nuova entità, converte json direttamente nell'entità java object tramite il processo di deserializzazione dove è richiesta la convalida richiesta. Ma quando aggiorna l'entità esistente, converte json in JsonNode e quindi si fonde con l'entità esistente e, come previsto, non avviene alcuna convalida perché è una funzione per la deserializzazione di JSON in oggetto java.

Come soluzione alternativa è inoltre possibile convertire JsonNode in oggetto entità e funzionerà come previsto.

Ho fatto un rapido esempio su come ottenere la convalida richiesta.

andare a https://github.com/valery-barysok/gs-accessing-data-rest

Non è una soluzione chiara, ma si può migliorare :)

Questo esempio ignorare molla classe esistente sul classpath org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver

Nota È must put questa classe su classpath prima della versione originale.

ho fatto copia-passato questa classe di progettare e modificare readPutForUpdate metodo:

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

e ho usato application.properties file per configurare DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

+0

Grazie per la tua risposta, ho temporaneamente risolto questo problema usando deserializzatori personalizzati, I force conversione in oggetto entità come nella tua soluzione. Devo scrivere deserializzatore per ogni classe di entità, ma non ho bisogno di scherzare con classpath in questo modo. – Infinity

+1

Questa è solo una idea per una soluzione che funziona ma puoi farlo in modo più corretto. È necessario eseguire l'override della configurazione di 'RepositoryRestMvcConfiguration' dove si fornisce la versione di' PersistentEntityResourceHandlerMethodArgumentResolver'. Ho provato a farlo in questo modo, ma ho bloccato con la configurazione errata di 'DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES'. –