我使用Java和Android Studio编写针对Google应用引擎的程序。Tools, Install Client Libraries从后端类创建前端模型。
现在,我已经意识到getter和setter总是作为类模型的一部分为客户端生成的,或者至少每当我使用getter时,会自动为同一属性生成setter。我理解REST需要在两边公开getter和setter来序列化和反序列化。
但是,如果我不希望客户端能够编写给定的属性,例如计数器,会发生什么?在连接的场景中,作为业务逻辑的一部分,我将省略该属性的setter。但似乎我被迫在这里实现它。
当然,在服务器上,我可以查看返回的对象,并在持久化它之前修改它,但我认为这没有任何意义。它不可能是解决方案,允许客户端设置一个属性,只是在以后保存之前将其剥离。
- 是否有办法阻止客户端访问setter?如何正确地做到这一点?
我知道我可以写一些代码来解决这个问题,但我正在寻找这个问题的最佳实践。
,
我又想起了另一件事,这更糟。甚至@Id(对象标识符,表示"主键")在客户端也有getter和setter。如果客户机从数据存储中获取对象,修改标识符并将其发送回后端,会发生什么情况?
- 我甚至无法在服务器端识别该对象。
- 恶意的客户端代码可以通过使后端更新错误的对象而完全破坏数据存储。
我不能相信这个没有合适的解决办法。
永远不能完全信任客户端数据。攻击者可以对REST调用的有效负载做任何他们想做的事情,因此您必须以某种方式实现安全性。没有真正的方法以自动化的方式做到这一点,因为用户可以对数据做什么取决于您的需求。您需要getter和setter将数据从rest字符串转换为对象,但来自用户的所有内容都必须用疲惫的眼睛来查看。
我是这样做的。
1)验证用户(如果您对用户进行身份验证)是否有权访问您正在更新的对象。他们是否有权更新该对象?如果它是一个新对象他们是否有权创建一个新对象?
2)假设他们有权限,只让他们更新可更新的部分。也就是说,他们不能更改创建/修改日期。这要由服务器来决定。通常我用id从数据库加载对象。然后复制与特定选项相关的所有字段。通常最好的方法是不让客户端直接更新对象。
有时候在你的api上有专门的动作要好得多。例如,假设这是一个bug跟踪系统。您不允许人们批量更新对象,而是提供诸如"关闭bug"之类的特定操作。这减少了通过线路传输的数据量,并使呼叫非常具体。这也意味着他们不能随意改变那些他们不应该改变的内部字段,比如当bug被打开或历史记录时。至于改变id,这意味着他们给你发送了一个不同的对象。您将对该对象进行验证,如果它是一个随机选择的数字,则该对象要么不存在,要么很可能不是属于该用户的对象,然后您将拒绝请求。
您不需要Setter,更不需要@Id -事实上,您根本不应该将数据存储id传递给客户端,因为这将不必要地向客户端透露数据存储的详细信息。
1。使用Key而不是Id——传递对象信息的最佳方式是使用它的Key而不是Id。Key可以使用以下命令计算:
Key<ObjClass> objKey = Key.create(this)
你可以安全地将它的字符串值传递给客户端:
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仅在对象作用域的上下文中有效。如果这听起来有点拗口,只需记住对象的Id仅在其父对象的作用域中是唯一的,因此,如果您的对象是其他对象的子对象,则不能仅使用Id直接从数据存储中检索它——这就足够了:您还需要父对象的Id, Key将包含父对象的Id。但是我离题了…
2。不要信任从客户端接收的对象
将通过REST/端点从客户端接收的对象仅仅视为数据结构。不要在业务逻辑中使用它们。在更新场景中,从数据存储中加载现有对象,然后仅应用您认为合适的来自接收到的对象的值:
@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。最后一点,关于你的评论
"只要我有两个之间的引用,解决方案就会失败实体。对于该操作,setter必须是可用的,否则I得到这个错误:message:[…]JsonMappingException:您不能创建@Id为空的对象的Key。"
看起来像一个GAE端点+ Objectify的场景,其中端点JSON解释器传入的有效载荷正在努力通过post提供的JSON中创建对象,因为Objectify由于缺乏信息而拒绝重新创建它们。也许端点正在尝试从一个子对象重构一个对象……