How to prevent client from changing objectify @ID

2019-09-02 21:57发布

问题:

I am using objectify to persist objects to Google Cloud datastore. The "primary key" is annotated with @Id.

@Entity
class Car {
    @Id Long id;
    ...
}

I generate the client endpoint (using Android Studio).

I need getters and setters for all fields, including id, so that REST is able to serialize and deserialize the object. If I don't add getters and setters, then these are created by the Google endpoint builder.

Normally, the client GETs an instance from datastore, changes attributes and UPDATEs it back. The backend processes the new attributes and stores them in datastore.

As long as the id is the same, this works as supposed. But what happens when the client changes the id? Because the server is stateless, it believes another object has been updated and either inserts or updates the wrong object. In other words, if the new id already exists in datastore, then that record is updated; if it doesn't exist then a new record with that id is created.

This behavior is inherent to datastore, but this will mess up the database if the client ever changes the id, so there must be a way to prevent this.

I can see two solutions:

  1. As suggested here GAE - Getter without Setter - How to prevent client from writing a given property? How to prevent client from modifying the object ID? by @zgc7009, I could leave the setter empty and let it do nothing. I have tried this and it works, as long as there are no related objects.

  2. I could use a version field on the backend, which I increment each time UPDATE is called. This allows me to verify if client version == server version - 1 and throw an exception if not. This measure would reduce chances of updating the wrong record, but it would require a read from the datastore before each write and therefore isn't efficient.

As both solutions are not nice, I believe somebody at Google has been thinking about this and there must be a better way to ensure data integrity across REST. Due to my understanding - and I don't know if that can be changed -, Google Endpoints API is generated in a way where the id is not included within the URL, or this happens under the cover. In other words: Normally the id under which an object is stored is included with the URL (metadata). Here, it is included with the object (payload) and can therefore be changed by the client.

Can anybody shed light please?

PS: I know that best practices for datastore in above example would probably using the VIN as id, but this is not always possible. There are objects where no unique identifier can be created for use as id.

回答1:

Looks like a common problem many have faced. There is one thing you can do:

Your solution 2 above is a good one but you're worried about reading from the Datastore before each write. This can be mitigated by annotating your entity class with @Cache so that the first time a client retrieves entities they will be cached. This way when the client calls your backend to update or do whatever you will not hit the Datastore when comparing the versions.



回答2:

If you are worried about exposing sensitive attributes over your endpoint you have 2 alternatives:

1) annotate them (getters) with @ApiResourceProperty(ignored=AnnotationBoolean.TRUE) so the endpoints serializer skips them.

2) Don't send your entities over the API, create basic POJOs with only the fields you want/need to transfer.