17.安全Security

安全

随着应用程序开发的进行,您将需要使用Parse的安全功能来保护数据。本文档介绍了如何保护应用程序。

如果您的应用遭到破坏,那么遭受损失的不仅仅是作为开发人员的你,还有应用的用户。在您的应用程序发布之前,请继续阅读我们在默认值和预防措施上的合理建议。

客户端 vs. 服务器

当应用程序首次连接到Parse时,它会使用ApplicationID和客户端密钥(REST Key、.NET Key或JavaScript Key,具体取决于您使用的平台)来确认自己的身份。这些并不是加密的,而且不能用来保护应用程序。这些密钥是作为您应用程序的一部分发送的,任何人都可以通过反编译您的应用或代理网络流量查找到您的客户端密钥。这个漏洞甚至更容易利用JavaScript发现 —— 只需在浏览器中“查看源代码”即可找到客户端密钥。

这就是为什么Parse有许多其他安全机制来帮助您保护数据。客户端密钥已经交给了您的用户,所以仅通过客户端密钥可以完成的操作都可以由一般公众、甚至恶意黑客实现。

另一方面,主密钥(Master Key)绝对是一种安全机制。使用主密钥可以绕应用程序的过所有安全机制,例如类级权限和ACL。有了主密钥就像拥有应用程序服务器的root权限,因此要像保护生产计算机的root密码一样来保护您的主密钥。

总体原则是限制客户端的权限(使用客户端密钥),在Cloud Code中执行任何敏感操作时应使用主密钥。您将在“在Cloud Code中实现业务逻辑”一节中学习如何最大限度地利用此功能。

最后一个注意事项:建议您在服务器中设置HTTPS和SSL,以避免中间人攻击,但是Parse在非HTTPS连接中也很好用。

1.Class-level权限

第二级的安全性在Schema和数据级别。执行此级别的安全措施将限制客户端应用程序如何以及何时在Parse中访问和创建数据。当您第一次开始开发Parse应用程序时,所有默认设置都已设置好为使您开发起来更高效。例如:

  • 客户端应用程序可以在Parse上创建新的类
  • 客户端应用程序可以向类添加字段
  • 客户端应用程序可以修改或查询Parse上的对象

您可以将任何这些权限应用到所有人、无人能用、或应用于您应用中的特定用户或角色。角色是包含用户或其他角色的组,您可以将其分配给对象以限制其使用。授予角色的任何权限也被授予其任何子级——无论他们是用户还是其他角色,都可以在应用上创建访问层级。Parse指南中包含了在应用程序中使用角色的详细说明。

一旦您确信您的应用程序中的类之间具有正确的类和关系,您就应该通过以下操作来将其锁定:

几乎每个你创建的类都应该在某种程度上调整这些权限。对于类中的每个对象具有相同权限的,类级别权限设置将是最高效的。例如,一个常见的用例是一个静态数据的类,任何人都可以读取,而没有任何人可写。

1.限制Class的创建

首先,您在应用程序中配置客户端不能在Parse上创建新类。可以通过在Parse Server配置中设置键allowClientClassCreation为false来完成。有关配置Parse Server的概述,请参阅自述文件。一旦设置此限制,类只能通过数据浏览器或masterKey创建。这样可以防止攻击者用无限制的随机新类填充数据库。

2.配置Class-level权限

Parse中,您可以指定每个类允许的操作。这可以限制客户端访问或修改类的方式。要更改这些设置,请转到数据浏览器,选择一个类,然后单击“安全”按钮。

您可以配置客户端对所选类执行以下每个操作的能力:

  • 读:

    • Get:使用Get权限,如果用户知道他们的objectIds,用户就可以在该表中获取对象。
    • Find:具有Find权限的任何人都可以查询表中的所有对象,即使他们不知道他们的objectIds。具有public Find权限的任何表都将被公众完全可读,除非您在每个对象上设置其ACL。
  • 写:

    • Update:具有Update权限的任何人都可以修改表中没有设置ACL的任何对象的字段。对于公开可读的数据,如游戏级别或资产,您应该禁用此权限。
    • Create:与Update类似,具有Create权限的任何人都可以在类中创建的对象。与Update权限一样,对于公开可读的数据,您应该关闭此权限。
    • Delete:通过此权限,你可以删除表中没有设置ACL的任何对象——只需要知道它的objectId。
  • Add fields:对象被创建时,Parse类会推定其模式。在开发应用程序时,这是非常好的,因为您可以向对象添加一个新的字段,而不必对后端进行任何更改。但是,一旦你发布了应用程序,就很少需要自动在类中添加新的字段。因此,当您发布应用程序后,应该几乎总是关闭所有C类的此权限。

对于上述每个操作,您都可以向所有用户(默认值)授予权限,也可以将权限锁定到一组角色和用户列表。例如,所有用户都可以使用的类可通过仅启用get和find来设置为只读,可通过仅允许create将logging类设置为只写。您可以在特定的一组用户或角色上设置update和delete权限来启用用户内容的审核功能。

2.Object-Level访问控制

前面您锁定了Schema和类级别的权限,现在就该考虑用户如何访问数据了。Object-Level访问控制使一个用户的数据与另一个用户的数据保持分离,因为有时候类中不同的对象只能被不同的人访问。例如,用户的私有个人数据只能由他们自己访问。

Parse还支持应用程序中的匿名用户存储和保护其特定数据而不需要显式登录。

当用户登录到应用程序时,它们会启动与Parse的会话。通过这个会话,他们可以添加和修改自己的数据,但阻止其修改其他用户的数据。

1.访问控制表(ACCESS CONTROL LISTS)

控制谁可以访问哪些数据最简单的方法就是通过访问控制列表(通常称为ACL)。ACL背后的思想是每个对象都有一组用户和角色的列表,以及用户或角色具有的权限。用户需要有读取权限(或必须属于具有读取权限的角色)才能检索一个对象的数据,同时用户需要写入权限(或必须属于具有写入权限的角色)才能更新或删除该对象。

拥有用户后,您可以开始使用ACL。记住:用户可通过传统的用户名/密码注册、Facebook或Twitter等第三方登录系统、甚至使用Parse的自动匿名用户功能创建。要将当前用户的数据ACL设置为不公开可读,您只需要执行以下操作:

var user = Parse.User.current();
user.setACL(new Parse.ACL(user));

大多数应用程序应该这样做。如果您存储任何敏感的用户数据(如电子邮件地址或电话号码),则需要设置这样的ACL,使得用户的私人信息对其他用户不可见。如果对象没有ACL,那么每个人都可以读写。唯一的例外是_User类。我们绝不允许用户写其他用户的数据,但默认情况下可以读取。(如果作为开发者你需要更新其他_User对象,请记住,您的主密钥就可以提供此功能)。

为了使每个对象都非常容易创建用户私有的ACL,我们可以设置一个默认的ACL,该ACL将用于您创建的每个新对象:

// not available in the JavaScript SDK

如果您希望用户同时拥有一些公开的数据和一些私有的数据,则最好有两个单独的对象。您可以在公共数据中添加一个指向私有数据的指针。

var privateData = Parse.Object.extend("PrivateUserData");
privateData.setACL(new Parse.ACL(Parse.User.current()));
privateData.set("phoneNumber", "555-5309");

Parse.User.current().set("privateData", privateData);

当然,您可以对对象设置不同的读写权限。例如,以下是如何为用户的公共帖子创建一个任何人都可以读取的ACL:

var acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setWriteAccess(Parse.User.current().id, true);

有时候,基于每个用户来管理权限不太方便,于是您希望使用具有同等权限的用户组(例如一组具有特殊权限的管理员)来实现。角色是一种特殊的对象,它让您可以创建能分配给ACL的一组用户。角色最棒的是,您可以从角色中添加和删除用户,而无需更新限制为该角色访问的每个对象。要创建只能由管理员才能写入的对象:

var acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setRoleWriteAccess("admins", true);

当然,这段代码假定你已经创建了一个名为“admins”的角色。当开发应用程序时,预先设置一小组特殊角色通常也是合理的。角色也可以即时创建和更新——例如,在每个连接已经建立后,还是可以将新朋友添加到“friendOf___”角色。

这一切只是一个开始。应用程序可以通过ACL和类级权限来强制执行各种复杂的访问模式。例如:

  • 对于私有数据,只有所有者可以读取和写入。
  • 对于留言板上的帖子,作者和“版主”角色的成员具有“写入”权限,一般公众具有“读取”权限。
  • 对于日志数据,仅能由开发人员通过使用主密钥的REST API访问,而ACL拒绝所有权限。
  • 由特权用户组或开发人员创建的数据,如当天的全局消息,可以具有公共读取权限,但限制只有“管理员”角色有写入权限。
  • 从一个用户发送到另一个用户的消息,只有这些用户拥有“读取”和“写入”权限。

举个例子,以下ACL的形式,它限制为仅所有者(由其objectId标识"aSaMpLeUsErId")有读取和写入权限、其他用户只能读取该对象:

{
    "*": { "read":true },
    "aSaMpLeUsErId": { "read" :true, "write": true }
}

以下是使用角色设置ACL形式的另一个示例:

{
    "role:RoleName": { "read": true },
    "aSaMpLeUsErId": { "read": true, "write": true }
}

2.指针权限

指针权限是一种特殊类型的类级权限,基于用户存储在这些对象的指针字段,它可以为类中的每个对象创建一个虚拟ACL。例如,给定一个带有owner字段的类,为owner字段设置读取指针权限,将使该类中的每个对象只能被该对象owner字段中的用户所读取。对于具有一个sender和一个reciever字段的类,receiver字段的读取指针权限和sender字段的读写指针权限,会使类中的每个对象让sender和receiver字段的用户可读,同时仅能让sender字段的用户可写。

鉴于对象通常已经有了指针,它指向对该对象有权限的用户,因此指针权限提供了一个简单而快速的解决方案,以便使用已经存在的数据保护您的应用程序,而不需要编写任何客户端代码或Cloud Code。

指针权限就像虚拟ACL。它们不会出现在ACL列中,如果您熟悉ACL的工作原理,您可以将其视为ACL。在上面sender和receiver的例子中,每个对象就相当于具有一个如下的ACL:

{
    "": {
        "read": true,
        "write": true
    },
    "": {
        "read": true
    }
}

请注意,这个ACL上并不是实际的在每个对象上创建。当您添加或删除指针权限时,任何现有的ACL都不会被修改,并且如果由指针权限创建的虚拟ACL和对象上已有的实际ACL同时允许,任何尝试与对象交互的用户只能与该对象交互。因此,有时可能会混淆指针权限和ACL,因此我们建议对没有设置很多ACL的类使用指针权限。幸运的是,当您以后决定使用Cloud Code或ACL来保护应用程序,删掉指针权限也很容易。

3.需要验证权限(需要PARSE-SERVER > = 2.3.0)

从2.3.0版本开始,parse-server引入了一个新的类级别权限requiresAuthentication。此CLP可防止任何未经身份验证的用户执行CLP保护的操作。

例如,你想要让经身份验证的用户,可从你的应用程序中find和get Announcement,而管理员角色拥有所有特权,可以这样设置CLP:

// POST http://my-parse-server.com/schemas/Announcement
// Set the X-Parse-Application-Id and X-Parse-Master-Key header
// body:
{
  classLevelPermissions:
  {
    "find": {
      "requiresAuthentication": true,
      "role:admin": true
    },
    "get": {
      "requiresAuthentication": true,
      "role:admin": true
    },
    "create": { "role:admin": true },
    "update": { "role:admin": true },
    "delete": { "role:admin": true },
  }
}

以上设置的效果:

  • 非验证用户将无法做任何事情。
  • 经过身份验证的用户(具有有效sessionToken的用户)将能够读取该类中的所有对象
  • 属于admin角色的用户能够执行所有操作。

警告:请注意,这样做绝对不能保护您的内容,如果允许任何人登录到您的服务器,那么每个客户端仍然可以查询此对象。

4.CLP和ACL交互

类级别权限(CLP)和访问控制列表(ACL)是保护应用程序的强大工具,但它们并不总是完全按您期望的方式进行交互。它们实际上表示每个请求必须通过两个独立的安全层,以返回正确的信息或进行预期的更改。这些层,一个在类级别,一个在对象级别,如下所示。请求必须通过这两个层的检查才能被授权。请注意,尽管行为类似于ACL,指针权限仍然是类级别许可的一种类型,因此请求必须通过指针权限检查才能通过CLP检查。

17.安全Security_第1张图片
clp_vs_acl_diagram.png

您可以看到,当您同时使用CLP和ACL时,用户是否有权提出一个请求可能会变得复杂。让我们通过一个例子来更好地了解CLP和ACL如何交互。假设我们有一个Photo类,它有一个对象photoObject;应用程序中有2个用户,user1和user2。我们在Photo类上设置一个Get CLP ,禁用public Get,但允许user1执行Get。同时,我们还在photoObject上设置一个ACL,只允许user2读权限(包括GET)。

您可能期望这将允许user1和user2都能Get photoObject,但是因为CLP层的认证和ACL层都会在任何时候起效,它实际上也使得user1也user2都不能获取photoObject。如果user1尝试Get photoObject,它将通过CLP层验证,但是会因通不过ACL层而被拒绝。同样的,如果user2尝试Get photoObject,它将在CLP层验证就被拒绝。

现在再看看使用指针权限的示例。假设我们有一个Post类,它有一个对象myPost。应用程序中有2个用户,poster和viewer。假设我们添加一个指针权限,它允许任何人在Post类的Creator字段有读取和写入权限,而对于myPost对象,poster是Creator字段的用户。对象上还有一个ACL,给予viewer读取权限。您可能期望这将允许poster读取和编辑myPost,同时viewer可读取它,但viewer将被指针权限拒绝,同时poster将被ACL拒绝,因此同样,两个用户都无法访问该对象。

由于CLP、指针权限和ACL之间的复杂交互,我们建议在一起使用时要小心。通常,仅使用CLP来禁用特定请求类型的所有权限,然后再对其他请求类型使用指针权限或ACL是很有用的。例如,您可能需要禁用Photo类的“删除” ,然后再在Photo上设置指针权限,使得创建它的用户可以对其进行编辑,但不能删除它。由于指针权限和ACL相互作用特别复杂,我们通常建议仅使用这两种安全机制中的一个。

5.安全边界案例

Parse中有一些特殊的类不遵循与其他类相同的安全规则。并不是所有的类都遵循类级别权限(CLP)或访问控制列表(ACL)定义的规则,以下就是一些例外。这里的“正常行为”是指CLP和ACL正常工作时,而脚注中则是其他特殊行为。

操作 _User _Installation
Get 正常行为[1,2,3] 忽略CLP,但不忽略ACL
Find 正常行为[3] 仅主密钥[6]
Create 正常行为[4] 忽略CLP
Update 正常行为[5] 忽略CLP,但不忽略ACL [7]
Delete 正常行为[5] 仅主密钥[7]
Add Field 正常行为 正常行为
  • [1] 登录或REST API中的/1/login,在User类上不遵守Get CLP。登录仅基于用户名和密码,不能使用CLP进行禁用。

  • [2] 检索当前用户或基于会话令牌(session token)的用户,都存在于REST API中的/1/users/me,在User类上不遵守Get CLP。

  • [3] Read ACL不适用于登录用户。例如,即使所有用户对象都具有禁用读取的ACL,对用户执行find查询仍将返回登录用户。但是,如果Find CLP被禁用,尝试对用户执行Find还是会返回错误。

  • [4] 创建CLP也适用于注册。因此,在User类中禁用Create CLP也会禁止用户在没有主密钥的情况下注册。

  • [5] 用户只能更新和删除自己。公共CLP的更新和删除可能仍然适用。例如,如果在User类禁用公共更新,则用户无法编辑自己。但是,无论在用户对象的ACL写入什么,该用户仍然可以更新或删除自己,同时其他用户都不能更新或删除该用户。然而,一如以往,使用主密钥用户可以更新其他用户,而不受CLP或ACL的影响。

  • [6] Installation上的Get请求正常遵循ACL。除非提供installationId作为约束,否则不允许没有主密钥执行Find请求。

  • [7] Installation上的Update请求完全遵守其上定义的ACL,但Delete请求仅限于主密钥。有关Installation如何工作的更多信息,请查看REST指南的installations部分。

3.Cloud Code中的数据完整性

对于大多数应用程序,仅关注密钥、类级别权限和对象级别ACL,就能保持应用程序和用户数据的安全。但有时候,对某些边缘案例,仅有他们还不够。对于这些情况,还好我们有“Cloud Code”。

Cloud Code允许您将JavaScript上传到Parse Server,服务器将为您运行。与在用户设备上运行客户端代码可能被篡改不同,“Cloud Code”保证运行您编写的代码,因此更加值得信赖。

Cloud Code的一个特别常见的用例是防止存储无效数据。对于这种情况,特别重要的一点是客户端恶意代码无法绕过验证逻辑。

要创建验证(validation)功能,Cloud Code允许在类上实现一个beforeSave触发器。每当保存对象时,都会运行这些触发器,并允许您修改该对象或完全拒绝保存操作。例如,以下创建了一个Cloud Code beforeSave触发器,以确保每个用户都设置了一个电子邮件地址:

Parse.Cloud.beforeSave(Parse.User, function(request, response) {
  var user = request.object;
  if (!user.get("email")) {
    response.error("Every user must have an email address.");
  } else {
    response.success();
  }
});

验证可以锁定您的应用程序,以便只接受某些特定值。您还可以使用afterSave验证来规范化您的数据(例如,格式化所有电话号码或统一货币)。您可以保留直接从客户端应用程序中访问Parse数据的大部分优势,但同时也可以即时强制规范数据的特定形式。

需要验证的常见场景包括:

  • 确保电话号码的格式正确
  • 净化数据,使其形式归一化
  • 确保电子邮件地址看起来像一个真正的电子邮件地址
  • 要求每个用户指定特定范围内的年龄
  • 不让用户直接更改计算字段
  • 不允许用户删除特定对象,除非满足某些条件

4.在Cloud Code中实现业务逻辑

虽然验证在Cloud Code中通常是有意义的,但是可能某些操作特别敏感,应尽可能小心保护。在这种情况下,您可以完全移除客户端的权限或逻辑,而是将所有这些操作转移到Cloud Code Function中。

当调用Cloud Code Function时,可以使用可选参数{useMasterKey:true}来获得修改用户数据的权限。使用主密钥,您的Cloud Code Function可以覆盖任何ACL并写入数据。这意味着它将绕过您在前几节中所设置的所有安全机制。

假设你想允许用户“like”一个Post对象,而不给他们对该对象的完全写入权限。您可以通过让客户端调用Cloud Code Function而不是修改Post本身来完成此操作:

应该小心地使用主密钥。仅在单次API函数调用需要覆写其安全机制时,才设置useMasterKey为true:

Parse.Cloud.define("like", function(request, response) {
  var post = new Parse.Object("Post");
  post.id = request.params.postId;
  post.increment("likes");
  post.save(null, { useMasterKey: true }).then(function() {
    // If I choose to do something else here, it won't be using
    // the master key and I'll be subject to ordinary security measures.
    response.success();
  }, function(error) {
    response.error(error);
  });
});

Cloud Code的一个非常常见的用例是向特定用户发送推送通知。一般来说,不能信任由客户端直接发送推送通知,因为他们能修改提示信息,或推送信息到不该推送的用户。您可以在应用程序设置中设定是否启用“客户端推送”; 我们建议您确保它已被禁用。相反,您应该编写Cloud Code Function,以在推送前验证一下即将推送并发出的数据。

5.Parse安全性总结

Parse提供了许多方法来保护您应用程序中的数据。当您构建应用程序并评估将要存储的数据种类时,可以决定选择何种实现方式。

值得重申的是,默认情况下,所有其他用户都可以读取Parse User对象。如果您希望防止User对象中的数据(例如,用户的电子邮件地址)被其他用户可见,您需要相应地在User对象上设置ACL。

您应用程序中大多数的类都将属于几种易于保障安全的类别。对于完全公开的数据,您可以使用类级别的权限来锁定表,以便公开可读但无人可写。对于完全私有数据,您可以使用ACL来确保只有拥有数据的用户可以读取它。但偶尔您会遇到不希望数据完全公开或完全私有的情况。例如,有一个社交应用,您拥有的用户数据应该只有他们已经批准的朋友才能读取。为此,您需要结合本指南中讨论的技术,才能实现所需的共享规则。

我们希望您可以尽可能地使用这些工具保护您应用数据和用户数据的安全。让我们一起构建一个更安全的互联网。

你可能感兴趣的:(17.安全Security)