Google cloud firestore 如何在云Firestore安全规则中实现写入速率限制?
我有一个应用程序,它使用Firebase SDK从应用程序内部直接与之对话。我的代码确保只以合理的间隔写入数据。但是恶意用户可能会从我的应用程序中获取配置数据,并使用它将源源不断的数据写入我的数据库Google cloud firestore 如何在云Firestore安全规则中实现写入速率限制?,google-cloud-firestore,firebase-security,Google Cloud Firestore,Firebase Security,我有一个应用程序,它使用Firebase SDK从应用程序内部直接与之对话。我的代码确保只以合理的间隔写入数据。但是恶意用户可能会从我的应用程序中获取配置数据,并使用它将源源不断的数据写入我的数据库 我如何确保用户只能每隔几秒钟写一次,而不必写任何服务器端代码。对数据库的每一次读写操作都由您为项目配置的服务器在谷歌服务器上进行验证。这些规则只能由项目的合作者设置,但适用于访问项目中数据库的所有客户端代码。这意味着您可以在这些安全规则中强制执行此条件,即使恶意用户也无法绕过它们,因为他们无权访问您
我如何确保用户只能每隔几秒钟写一次,而不必写任何服务器端代码。对数据库的每一次读写操作都由您为项目配置的服务器在谷歌服务器上进行验证。这些规则只能由项目的合作者设置,但适用于访问项目中数据库的所有客户端代码。这意味着您可以在这些安全规则中强制执行此条件,即使恶意用户也无法绕过它们,因为他们无权访问您的项目 假设我们有一个
users
集合,其中的每个文档都有一个ID和用户的UID。这些安全规则确保用户只能编写自己的文档,并且每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');
}
}
演练可能有助于:
/users
集合中的所有文档
isMine()
),如果它有时间戳(hastinestamp()
),用户可以创建文档
isCalm()
)
让我们依次看看这三个函数
isMine()
函数检查文档ID是否与执行写入操作的用户相同。由于Firebase根据登录的用户自动填充auth.uid
,因此恶意用户无法伪造此值
request.resource
)是否有时间戳字段,如果有,时间戳是否与当前服务器端时间相同。这意味着在代码中,您需要指定FieldValue.serverTimestamp()
,才能接受写入。因此,您只能写入当前的服务器端时间戳,恶意用户不能传递不同的时间戳
isCalm()
函数确保用户不经常写入。如果现有文档(resource.data.timestamp
)中的timestamp
值与当前正在写入的文档(request.resource.data.timestamp
)之间的差异至少为5秒,则允许写入
需要注意的是,上面实现了每个文档的写入限制,而不是每个帐户的写入限制。用户仍然可以在系统允许的范围内以最快的速度编写其他文档 如果您想对用户写入的所有文档设置每用户写入速率限制,请继续阅读
下面是我如何测试这些规则的jsbin:。使用此代码:
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秒时,才会允许下次写入
当我大约每秒单击一次Run按钮时,我得到:
“写于2019年6月6日星期四20:20:19 GMT-0700(太平洋夏令时)”
“权限被拒绝”
“权限被拒绝”
“权限被拒绝”
“权限被拒绝”
“写于2019年6月6日星期四20:20:24 GMT-0700(太平洋夏令时)”
“权限被拒绝”
“权限被拒绝”
“权限被拒绝”
“权限被拒绝”
“写于2019年6月6日星期四20:20:30 GMT-0700(太平洋夏令时)”
最后一个示例是每个用户的写入速率限制。假设您有一个社交媒体应用程序,其中用户创建帖子,每个用户都有一个个人资料。所以我们有两个集合:
posts
和users
。我们希望确保用户最多每5秒创建一次新帖子
这方面的规则与以前基本相同,如:用户可以更新自己的个人资料,如果在过去5秒钟内没有写过文章,则可以创建帖子
最大的不同是我们将时间戳存储在他们的用户配置文件(/users/$uid
)中,即使他们正在创建新的post文档(/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()
和hastinsmstamp()
的实现与以前相同。但是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');
}
需要注意的几点:
- 就像之前我们比较两个时间戳一样。但这里我们从不同的文档中读取时间戳
- 这需要读取两个额外的文档,这意味着您需要为两个额外的读取操作付费。如果速率限制的目的是不对恶意用户的写入操作收费,那么这可能不是您正在寻找的解决方案