GAE - 没有Setter的Getter - 如何防止客户编写给定的属性?如何防止客户端修改对象ID?

时间:2014-05-15 18:51:28

标签: java android google-app-engine properties google-cloud-endpoints

我正在使用Java和Android Studio对抗Google App Engine。工具,安装客户端库为后端类的前端创建模型。这很有效。

现在,我已经意识到getter和setter总是作为类模型的一部分为客户端生成,或者至少每当我使用getter时,都会为同一属性自动生成setter。我知道REST需要让getter和setter暴露在双方的序列化和反序列化中。

但是,如果我不希望客户能够编写给定的属性(例如计数器),会发生什么?在连接的场景中,作为业务逻辑的一部分,我会省略该属性的setter。但似乎我不得不在这里实施它。

当然,在服务器上我可以查看返回的对象,并在保留之前对其进行修改,但我认为这没有任何意义。它不是允许客户端设置属性的解决方案,只是稍后在保存之前将其剥离。

  • 有没有办法阻止客户端访问setter?这是怎么做到的?

我知道我可以编写一些代码来解决这个问题,但我正在寻找这个问题的最佳实践。

-

我想到了其他一些事情,这更糟糕。甚至@Id(对象标识符,说"主键")在客户端也有getter和setter。如果客户端从数据存储区获取对象,修改标识符并将其发送回后端会发生什么?

  1. 我甚至无法在服务器端识别该对象。
  2. 恶意客户端代码可能会使后端更新错误的对象,从而完全搞乱数据存储。
  3. 我无法相信没有适当的解决方案。

2 个答案:

答案 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正在尝试从子对象重建一个对象......这正是上面解决的例子。