如何在Cloud Firestore安全规则中实施写入速率限制?

时间:2019-06-07 03:26:02

标签: google-cloud-firestore firebase-security-rules

我有一个使用Firebase SDK从应用程序内部直接与Cloud Firestore对话的应用程序。我的代码确保仅以合理的间隔写入数据。但是恶意用户可能会从我的应用程序中获取配置数据,然后使用它将无尽的数据流写入我的数据库。

我如何确保用户每隔几秒钟只能写一次发言,而不必编写任何服务器端代码。

1 个答案:

答案 0 :(得分:7)

对数据库的每次读或写操作都将通过为项目配置的security rules在Google的服务器上进行验证。这些规则只能由项目的协作者设置,但适用于访问项目中数据库的所有客户端代码。这意味着您可以在这些安全规则中强制执行此条件,即使恶意用户也无法绕过它们,因为它们无权访问您的项目。

假设我们有一个users集合,并且那里的每个文档都有一个带有用户UID的ID。这些安全规则确保用户只能编写自己的文档,并且每5秒只能写一次。

match /users/{document=**} {
  allow create: if isMine() && hasTimestamp();
  allow update: if isMine() && hasTimestamp() && isCalm();
  function isMine() {
    return request.resource.id == request.auth.uid;
  }
  function hasTimestamp() {
    return request.resource.data.timestamp == request.time;
  }
  function isCalm() {
    return request.time > resource.data.timestamp + duration.value(5, 's');
  }
}

演练可能会有所帮助:

  1. 第一行确定其中的规则范围,因此这些规则适用于/users集合中的所有文档。

  2. 如果是文档(isMine(),如果有时间戳(hasTimestamp()),则用户可以创建文档。

  3. 用户可以更新文档(如果文档属于自己的文档,具有时间戳记并且不经常写(isCalm())。

    让我们依次看一下这三个功能...

  4. isMine()函数检查文档ID是否与执行写操作的用户相同。由于Firebase会根据登录的用户自动填充auth.uid,因此恶意用户无法欺骗该值。

  5. hasTimestamp()函数检查正在写入的文档(request.resource)是否具有时间戳记字段,如果有,则该时间戳记与当前服务器端时间相同。 。这意味着在代码中,您需要指定FieldValue.serverTimestamp()才能使写入可接受。因此,您只能编写当前的服务器端时间戳,而恶意用户则不能传递其他时间戳。

  6. isCalm()函数可确保用户不会写得太频繁。如果现有文档(timestamp)中的resource.data.timestamp值与当前正在写入的文档(request.resource.data.timestamp)之间的firebase.auth().signInAnonymously().then(function(auth) { var doc = collection.doc(auth.user.uid); doc.set({ timestamp: firebase.firestore.FieldValue.serverTimestamp() }).then(function() { console.log("Written at "+new Date()); }).catch(function(error) { console.error(error.code); }) }) 值之间的差值至少为5秒,则允许写入。

每道格的评论:

  

重要的是要注意,以上实现的是每个文档的写限制,而不是每个帐户的写限制。用户仍然可以按照系统允许的速度自由编写其他文档。

如果要对每个用户写入的文档都设置一个按用户的写入速率限制,请继续阅读。


以下是我测试这些规则的方式:https://jsbin.com/kejobej/2/edit?js,console。使用以下代码:

posts

如果您反复单击 Run 按钮,则仅在自上一次写入后至少经过5秒钟时才允许下一次写入。

当我大约每秒单击一次“运行”按钮时,我得到了:

  

“写于格林尼治标准时间2019年6月6日20:20:19(太平洋夏令时)”

     

“权限被拒绝”

     

“权限被拒绝”

     

“权限被拒绝”

     

“权限被拒绝”

     

“写于2019年6月6日星期四20:20:24 GMT-0700(太平洋夏令时)”

     

“权限被拒绝”

     

“权限被拒绝”

     

“权限被拒绝”

     

“权限被拒绝”

     

“写于格林尼治标准时间2019年6月6日20:20:30(太平洋夏令时间)”


最后一个示例是每个用户的写入速率限制。假设您有一个社交媒体应用程序,用户在其中创建帖子,每个用户都有一个个人资料。因此,我们有两个集合:users/users/$uid。并且我们希望确保用户最多每5秒钟可以创建一次新帖子。

此规则与以前几乎相同,如下所示:用户可以更新自己的个人资料,如果过去5秒钟内没有写任何帖子,则可以创建一个帖子。

最大的不同是,即使他们正在创建新的邮政文档(/posts/$newid),我们也将时间戳存储在他们的用户个人资料(BatchedWrite)中。由于这两次写操作都必须合而为一,因此这次我们将使用var root = firebase.firestore(); var users = root.collection("users"); var posts = root.collection("posts"); firebase.auth().signInAnonymously().then(function(auth) { var batch = db.batch(); var userDoc = users.doc(auth.user.uid); batch.set(userDoc, { timestamp: firebase.firestore.FieldValue.serverTimestamp() }) batch.set(posts.doc(), { title: "Hello world" }); batch.commit().then(function() { console.log("Written at "+new Date()); }).catch(function(error) { console.error(error.code); }) })

match /users/{user} {
  allow write: if isMine() && hasTimestamp();
}
match /posts/{post} {
    allow write: if isCalm();
}

所以批处理中写了两件事:

  • 它将当前服务器端时间写入用户的个人资料。
  • 它将创建一个带有标题字段的新帖子。

(如上所述)用于此目的的最高安全规则与以前几乎相同:

isMine()

因此,用户可以写入个人资料文档(如果该文档属于自己的文档),并且该文档包含等于当前服务器端/请求时间的时间戳。如果用户最近未发布过帖子,则可以撰写。

hasTimstamp()isCalm()的实现与以前相同。但是,function isCalm() { return getAfter(/databases/$(database)/documents/users/$(request.auth.uid)).data.timestamp > get(/databases/$(database)/documents/users/$(request.auth.uid)).data.timestamp + duration.value(5, 's'); } 的实现现在在写操作之前和之后都查找用户配置文件文档以进行时间戳检查:

get()

不幸的是,到达getAfter()// These won't work, but are easier to read. function isCalm() { return getAfter(/users/$(request.auth.uid)).data.timestamp > get(/users/$(request.auth.uid)).data.timestamp + duration.value(5, 's'); } 的路径必须是绝对且完全限定的,但可以归结为:

{{1}}

一些注意事项:

  • 就像我们要比较两个时间戳之前一样。但是,这里我们从不同的文档中读取时间戳。
  • 这需要阅读两个额外的文档,这意味着您需要支付两次额外的阅读操作费用。如果不为恶意用户的写操作收取费用限制的目的,那么这可能不是您想要的解决方案。