如何在 Cloud Firestore 安全规则中实施写入速率限制? [英] How do I implement a write rate limit in Cloud Firestore security rules?

查看:17
本文介绍了如何在 Cloud Firestore 安全规则中实施写入速率限制?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an app that uses the Firebase SDK to directly talk to Cloud Firestore from within the application. My code makes sure to only write data at reasonable intervals. But a malicious user might take the configuration data from my app, and use it to write an endless stream of data to my database.

如何确保用户每隔几秒只能写一次 say ,而不必编写任何服务器端代码.

How can I make sure a user can only write say once every few seconds, without having to write any server-side code.

推荐答案

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

Every read or write operation to your database, is validated on Google's servers by the security rules that you configured for your project. These rules can only be set by collaborators on your project, but apply to all client-side code that accesses the database in your project. This means that you can enforce this condition in these security rules, not even the malicious user can bypass them, since they don't have access to your project.

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

Say we have a users collection, and that each document in there has an ID with the UID of the user. These security rules make sure that the user can only write their own document, and no more than once every 5 seconds:

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 集合中的所有文档.

用户可以创建属于他们的文档(isMine()),如果它有时间戳(hasTimestamp()).

A user can create a document if it's theirs (isMine()), if it has a timestamp (hasTimestamp()).

用户可以更新文档,如果它是他们的,有时间戳,并且如果他们不经常写(isCalm()).

A user can update a document, if it's theirs, has a timestamp, and and if they don't write too often (isCalm()).

我们依次看一下这三个函数...

Let's look at all three functions in turn...

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

The isMine() function checks if the document ID is the same as the user who is performing the write operation. Since auth.uid is populated by Firebase automatically based on the user who is signed in, there is no way for a malicious user to spoof this value.

hasTimestamp() 函数检查正在编写的文档 (request.resource) 是否有时间戳字段,如果有,是否有时间戳与当前服务器端时间相同.这意味着在代码中,您需要指定 FieldValue.serverTimestamp() 以使写入可接受.所以只能写入当前服务器端时间戳,恶意用户无法传入不同的时间戳.

The hasTimestamp() function checks if the document that is being written (request.resource) has a timestamp field, and if so, if that timestamp is the same as the current server-side time. This means that in code, you will need to specify FieldValue.serverTimestamp() in order for the write to be acceptable. So you can only write the current server-side timestamp, and a malicious user can't pass in a different timestamp.

isCalm() 函数确保用户不会写得太频繁.如果现有文档(resource.data.timestamp)和文档(request.resource.data.timestamp)中的 timestamp 值之间存在差异,它允许写入) 当前正在写入的代码至少为 5 秒.

The isCalm() functions makes sure the user doesn't write too often. It allows the write if the difference between the timestamp values in the existing document (resource.data.timestamp) and the document (request.resource.data.timestamp) that is currently being written, is at least 5 seconds.

根据道格的评论:

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

It's important to note that the above implements a per-document write limit, and not a per-account limit. The user is still free to write other documents as fast as the system allows.

如果您想对他们编写的所有文档设置每个用户的写入速率限制,请继续阅读.

Continue reading if you want to have a per-user write rate-limit, on all documents they write.

这是我如何测试这些规则的 jsbin:https://jsbin.com/kejobej/2/edit?js,控制台.使用此代码:

Here's a jsbin of how I tested these rules: https://jsbin.com/kejobej/2/edit?js,console. With this code:

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 秒后才会允许下一次写入.

If you repeatedly click the Run button, it will only allow a next write if at least 5 seconds have passed since the previous one.

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

When I click the Run button about once a second, I got:

写于 2019 年 6 月 6 日星期四 20:20:19 GMT-0700(太平洋夏令时间)"

"Written at Thu Jun 06 2019 20:20:19 GMT-0700 (Pacific Daylight Time)"

权限被拒绝"

权限被拒绝"

权限被拒绝"

权限被拒绝"

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

"Written at Thu Jun 06 2019 20:20:24 GMT-0700 (Pacific Daylight Time)"

权限被拒绝"

权限被拒绝"

权限被拒绝"

权限被拒绝"

写于 2019 年 6 月 6 日星期四 20:20:30 GMT-0700(太平洋夏令时间)"

"Written at Thu Jun 06 2019 20:20:30 GMT-0700 (Pacific Daylight Time)"


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


The final example is a per-user write rate-limit. Say you have a social media application, where users create posts, and each user has a profile. So we have two collections: posts and users. And we want to ensure that a user can create a new post at most once every 5 seconds.

此操作的规则与以前几乎相同,例如:用户可以更新自己的个人资料,如果在过去 5 秒内没有写过,则可以创建帖子.

The rules for this are pretty much the same as before, as in: a user can update their own profile, and can create a post if they haven't written one in the past 5 seconds.

最大的不同在于我们将时间戳存储在他们的用户配置文件 (/users/$uid) 中,即使他们正在创建新的帖子文档 (/posts/$newid).由于这两个写入都需要同时进行,因此这次我们将使用 BatchedWrite:

The big different is that we store the timestamp in their user profile (/users/$uid), even when they're creating a new post document (/posts/$newid). Since both of these writes need to happen as one, we'll use a BatchedWrite this time around:

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);
  })
})

所以批处理写了两件事:

So the batch writes two things:

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

对此的顶级安全规则(如前所述)与以前几乎相同:

The top-level security rules for this are (as said) pretty much the same as before:

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

因此,如果用户自己的配置文件文档,并且该文档包含等于当前服务器端/请求时间的时间戳,则用户可以写入配置文件文档.用户可以写一篇文章,如果他们最近还没有发帖的话.

So a user can write to a profile doc if it's their own, and if that doc contains a timestamp that is equal to the current server-side/request time. A user can write a post, if they haven't posted too recently.

isMine()hasTimstamp() 的实现与之前相同.但是 isCalm() 的实现现在在写入操作之前和之后查找用户配置文件以进行时间戳检查:

The implementation of isMine() and hasTimstamp() is the same as before. But the implementation of isCalm() now looks up the user profile document both before and after the write operation to do its timestamp check:

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() 的路径必须是绝对的和完全限定的,但归结为:

The path to get() and getAfter() unfortunately has to be absolute and fully qualified, but it boils down to this:

// 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');
}

需要注意的几点:

  • 就像之前我们比较两个时间戳一样.但在这里,我们正在读取来自不同文档的时间戳.
  • 这需要阅读两个额外的文档,这意味着您需要为两次额外的阅读操作付费.如果速率限制的目的是不对恶意用户的写入操作收费,这可能不是您要寻找的解决方案.

这篇关于如何在 Cloud Firestore 安全规则中实施写入速率限制?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆