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

查看:55
本文介绍了如何在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.

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

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

推荐答案

对数据库的每次读写操作均通过

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是否与执行写操作的用户相同.由于Firebase会根据登录的用户自动填充auth.uid,因此恶意用户无法欺骗该值.

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

如果您反复单击 Run 按钮,则只有在上一次写入之后至少经过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(太平洋夏令时间)"

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

拒绝权限"

拒绝权限"

拒绝权限"

拒绝权限"

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

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

拒绝权限"

拒绝权限"

拒绝权限"

拒绝权限"

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

"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.

最大的不同是,即使他们正在创建新的邮政文档(/posts/$newid),我们也将时间戳存储在他们的用户个人资料(/users/$uid)中.由于这两种写操作都必须合而为一,因此这次我们将使用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天全站免登陆