我正在使用Java和Android Studio对抗Google App Engine。工具,安装客户端库为后端类的前端创建模型。这很有效。
现在,我已经意识到getter和setter总是作为类模型的一部分为客户端生成,或者至少每当我使用getter时,都会为同一属性自动生成setter。我知道REST需要让getter和setter暴露在双方的序列化和反序列化中。
但是,如果我不希望客户能够编写给定的属性(例如计数器),会发生什么?在连接的场景中,作为业务逻辑的一部分,我会省略该属性的setter。但似乎我不得不在这里实施它。
当然,在服务器上我可以查看返回的对象,并在保留之前对其进行修改,但我认为这没有任何意义。它不是允许客户端设置属性的解决方案,只是稍后在保存之前将其剥离。
我知道我可以编写一些代码来解决这个问题,但我正在寻找这个问题的最佳实践。
-
我想到了其他一些事情,这更糟糕。甚至@Id(对象标识符,说"主键")在客户端也有getter和setter。如果客户端从数据存储区获取对象,修改标识符并将其发送回后端会发生什么?
我无法相信没有适当的解决方案。
答案 0 :(得分:0)
您永远不能完全信任客户数据。攻击者可以对REST调用的有效负载执行任何操作,因此您必须以某种方式实现安全性。没有真正的方法以自动方式执行此操作,因为用户可以对您的数据执行的操作符合您的要求。您需要getter和setter将数据从其余字符串转换为您的对象,但是来自用户的所有内容都必须以疲惫的眼光来查看。
这就是我的所作所为。
1)验证用户(如果您对用户进行身份验证)是否可以访问您要更新的对象。他们是否有权更新该对象?如果它是新对象,他们是否有权创建新对象?
2)假设他们有权限,只让他们更新可更新的部分。即他们无法改变创建/修改日期。这是服务器决定的。所以我通常使用它的id从数据库加载对象。然后我复制与特定选项相关的所有字段。通常最好的想法是不要让客户端直接更新对象。
有时候对你的api采取专门的行动要好得多。例如,让我们说它是一个错误跟踪系统。你不允许人们批发更新对象,但提供特定的操作,例如"关闭bug。"这减少了线路上的数据量,使呼叫非常具体。这也意味着他们无法解决所有内部领域,他们不应该像打开错误或历史时那样改变。
至于更改id意味着他们正在向您发送不同的对象。您将对该对象进行验证,如果它是随机选择的数字,则该对象将不存在或者很可能不会成为属于该用户的对象,并且您将拒绝该请求。
答案 1 :(得分:0)
您不需要Setter,更不用说@Id了 - 事实上,您根本不应该将数据存储区ID传递给客户端,因为这会不必要地向客户端显示数据存储区的详细信息。
<强> 1。使用Key而不是Id - 传递有关对象信息的最佳方法是使用其Key而不是Id。可以使用以下方法计算密钥:
Key<ObjClass> objKey = Key.create(this)
您可以安全地将其String值传递给客户端:
String keyString = Key.create(this).getString()
Objectify将此称为 webString ,因为它对通过线路传输很有用。一旦你找回它,你可以使用:
检索对象Key<ObjClass> objKey = Key.create(keyString);
ObjClass obj = ofy().load().key(objKey).now();
您总是希望传递Key而不是Id的另一个原因是,Key将包含层次结构信息,而Id仅在对象范围的上下文中有效。如果这听起来像是满口,只需要注意,Object的Id在它的Parent对象的范围内是唯一的,所以如果你的对象是某个其他对象的Child,你不能直接从只有Id的数据存储中检索它 - 这就足够了:你也需要父的ID,Key将包含它。但我离题了......
<强> 2。不信任从客户端收到的对象
将通过REST /端点从客户端接收的对象视为数据结构。不要在业务逻辑上使用它们。在更新方案中,从数据存储区加载现有对象,然后根据需要仅应用接收到的Object中的值:
@ApiMethod(...)
public SomeRes updateChildObj(ChildObj childObj) {
// Retrieve the ChildObj from the Datastore (see further below for code):
ChildObj existingChildObj = ChildObj.getChildObj(childObj.getKey());
// Copy the values between Objects, but otherwise keep them separate:
existingChildObj.setSafeParam(childObj.getSafeParam());
// Save the existing Object back to the Datastore:
existingChildObj.save();
}
第3。全部放在一起
所以这里有一个Parent和Child对象的例子(或带有引用的对象,对于所有重要的,或者在您的场景中可能):
@Entity
class ParentObj {
// This ensures that the client does not get to see Id:
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
@Id Long id = null;
// The client sees the Key, but it won't be saved on the Datastore:
@Ignore String key = null;
public Long getId() {
return id;
}
public String getKey() {
// Can only generate a Key if the object has been saved:
return id != null ? Key.create(this).getString() : key;
}
public String setKey() {
this.key = key;
}
public static ParentObj getParentObj(String keyStr) {
Key<ParentObj> parentObjKey = Key.create(keyStr);
return ofy().load().key(parentObjKey).now();
}
}
@Entity
class ChildObj {
// Same tricks as ParentObj:
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
@Id Long id = null;
@Ignore String key = null;
// Same stuff again:
public Long getId() { return id; }
public String getKey() { return id != null ? Key.create(this).getString() : key; }
public String setKey() { this.key = key; }
// Now the Parent/Ref:
@Parent @Index private Ref<ParentObj> parentObj = null;
public Account getParentObj() {
// Straight get() from the reference
return parentObj.get();
}
public void setParentObj(ParentObj parentObj) {
checkNotNull(account);
checkNotNull(account.getKey()); // Check that we got a key
if (account.getId() == null) {
// We don't have an Id -- it's an empty shell, so we need to
// retrieve the actual object, otherwise you get the error:
// You cannot create a Key for an object with a null @Id
this.parentObj = Ref.create(ParentObj.getParentObj(parentObj.getKey()));
} else {
// We have an actual object form the Datastore, so we can just Ref to it:
this.parentObj = Ref.create(parentObj);
}
}
}
<强> 4。最后一点,关于您的评论
“只要我在两者之间有参考,解决方案就会失败 实体。对于那个操作,设置器必须可用,否则我 得到这个错误:消息:[...] JsonMappingException:你无法创建 一个具有null @Id的对象的键。“
似乎是GAE Endpoints + Objectify场景,其中传入有效负载的端点JSON解释器正在努力通过post提供JSON中的对象,因为Objectify由于缺乏信息而拒绝重新创建它们。也许Endpoints正在尝试从子对象重建一个对象......这正是上面解决的例子。