安全规则中的Firebase速率限制?
我启动了我的第一个开放存储库项目,人们立即开始向它发送大量请求 Firebase是否有办法在安全规则中对限制请求进行评级?我假设有一种方法可以使用请求时间和以前写入数据的时间来完成,但在文档中找不到任何关于如何完成此操作的信息 目前的安全规则如下安全规则中的Firebase速率限制?,firebase,firebase-realtime-database,firebase-security,rate-limiting,Firebase,Firebase Realtime Database,Firebase Security,Rate Limiting,我启动了我的第一个开放存储库项目,人们立即开始向它发送大量请求 Firebase是否有办法在安全规则中对限制请求进行评级?我假设有一种方法可以使用请求时间和以前写入数据的时间来完成,但在文档中找不到任何关于如何完成此操作的信息 目前的安全规则如下 { "rules": { "rooms": { "$RoomId": { "connections": {
{
"rules": {
"rooms": {
"$RoomId": {
"connections": {
".read": true,
".write": "auth.username == newData.child('FBUserId').val()"
},
"messages": {
"$any": {
".write": "!newData.exists() || root.child('rooms').child(newData.child('RoomId').val()).child('connections').hasChild(newData.child('FBUserId').val())",
".validate": "newData.hasChildren(['RoomId','FBUserId','userName','userId','message']) && newData.child('message').val().length >= 1",
".read": "root.child('rooms').child(data.child('RoomId').val()).child('connections').hasChild(data.child('FBUserId').val())"
}
},
"poll": {
".write": "auth.username == newData.child('FBUserId').val()",
".read": true
}
}
}
}
}
我想对整个Rooms对象的db写入(和读取?)进行分级,这样每秒只能发出一个请求(例如)。诀窍是对用户上次发布消息的时间进行审核。然后,您可以根据审核值强制每个邮件的发布时间:
{
"rules": {
// this stores the last message I sent so I can throttle them by timestamp
"last_message": {
"$user": {
// timestamp can't be deleted or I could just recreate it to bypass our throttle
".write": "newData.exists() && auth.uid === $user",
// the new value must be at least 5000 milliseconds after the last (no more than one message every five seconds)
// the new value must be before now (it will be since `now` is when it reaches the server unless I try to cheat)
".validate": "newData.isNumber() && newData.val() === now && (!data.exists() || newData.val() > data.val()+5000)"
}
},
"messages": {
"$message_id": {
// message must have a timestamp attribute and a sender attribute
".write": "newData.hasChildren(['timestamp', 'sender', 'message'])",
"sender": {
".validate": "newData.val() === auth.uid"
},
"timestamp": {
// in order to write a message, I must first make an entry in timestamp_index
// additionally, that message must be within 500ms of now, which means I can't
// just re-use the same one over and over, thus, we've effectively required messages
// to be 5 seconds apart
".validate": "newData.val() >= now - 500 && newData.val() === data.parent().parent().parent().child('last_message/'+auth.uid).val()"
},
"message": {
".validate": "newData.isString() && newData.val().length < 500"
},
"$other": {
".validate": false
}
}
}
}
}
我没有足够的声誉来写评论,但我同意维克托的评论。如果您将
fb.child('messages').push(…)
插入一个循环(即for(让i=0;i<100;i++){…}
),那么它将成功地推送60-80条Meessage(在500毫秒的窗口框架中)
受加藤方案的启发,我提议对规则进行如下修改:
rules: {
users: {
"$uid": {
"timestamp": { // similar to Kato's answer
".write": "auth.uid === $uid && newData.exists()"
,".read": "auth.uid === $uid"
,".validate": "newData.hasChildren(['time', 'key'])"
,"time": {
".validate": "newData.isNumber() && newData.val() === now && (!data.exists() || newData.val() > data.val() + 1000)"
}
,"key": {
}
}
,"messages": {
"$key": { /// this key has to be the same is the key in timestamp (checked by .validate)
".write": "auth.uid === $uid && !data.exists()" ///only 'create' allow
,".validate": "newData.hasChildren(['message']) && $key === root.child('/users/' + $uid + '/timestamp/key').val()"
,"message": { ".validate": "newData.isString()" }
/// ...and any other datas such as 'time', 'to'....
}
}
}
}
}
js代码与Kato的解决方案非常相似,只是getTimestamp将返回{time:number,key:string}到下一个回调
此解决方案避免了500毫秒的时间窗口,我们不必担心客户端必须足够快才能在500毫秒内推送消息,它们只能在
消息中写入一个单键。或者,消息中的仅创建规则可防止这种情况发生。现有答案使用两个数据库更新:(1)标记时间戳,和(2)将标记的时间戳附加到实际写入。需要500毫秒的时间窗口,而ChiNhan需要记住下一个键
有一种更简单的方法可以在单个数据库更新中完成。其思想是使用该方法一次向数据库写入多个值。安全规则验证写入的值,以便写入不会超过配额。配额被定义为一对值:quotaTimestamp
和postCount
Count
是在quotaTimestamp
后1分钟内写入的帖子数。如果后计数超过某个值,则安全规则只会拒绝下一次写入。当quotaTimestamp超过1分钟时,后计数将重置
以下是如何发布新消息:
function postMessage(user, message) {
const now = Date.now() + serverTimeOffset;
if (!user.quotaTimestamp || user.quotaTimestamp + 60 * 1000 < now) {
// Resets the quota when 1 minute has elapsed since the quotaTimestamp.
user.quotaTimestamp = database.ServerValue.TIMESTAMP;
user.postCount = 0;
}
user.postCount++;
const values = {};
const messageId = // generate unique id
values[`users/${user.uid}/quotaTimestamp`] = user.quotaTimestamp;
values[`users/${user.uid}/postCount`] = user.postCount;
values[`messages/${messageId}`] = {
sender: ...,
message: ...,
...
};
return this.db.database.ref().update(values);
}
函数postMessage(用户、消息){
const now=Date.now()+serverTimeOffset;
如果(!user.quotaTimestamp | user.quotaTimestamp+60*1000<现在){
//在quotaTimestamp之后1分钟后重置配额。
user.quotaTimestamp=database.ServerValue.TIMESTAMP;
user.postCount=0;
}
user.postCount++;
常量值={};
const messageId=//生成唯一id
值[`users/${user.uid}/quotaTimestamp`]=user.quotaTimestamp;
值[`users/${user.uid}/postCount`]=user.postCount;
值[`messages/${messageId}`]={
发件人:。。。,
信息:。。。,
...
};
返回此.db.database.ref()更新(值);
}
分级的安全规则限制为每分钟最多5个帖子:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid && newData.child('postCount').val() <= 5",
"quotaTimestamp": {
// Only allow updating quotaTimestamp if it's staler than 1 minute.
".validate": "
newData.isNumber()
&& (newData.val() === now
? (data.val() + 60 * 1000 < now)
: (data.val() == newData.val()))"
},
"postCount": {
// Only allow postCount to be incremented by 1
// or reset to 1 when the quotaTimestamp is being refreshed.
".validate": "
newData.isNumber()
&& (data.exists()
? (data.val() + 1 === newData.val()
|| (newData.val() === 1
&& newData.parent().child('quotaTimestamp').val() === now))
: (newData.val() === 1))"
},
"$other": { ".validate": false }
}
},
"messages": {
...
}
}
}
{
“规则”:{
“用户”:{
“$uid”:{
“.read”:“$uid==auth.uid”,
“.write”:“$uid===auth.uid&&newData.child('postCount').val()我喜欢它,但它没有考虑到恶意用户仅仅使用for循环就在500ms窗口之间泛滥聊天。
我提出的这一变体消除了这种可能性:
{
"rules": {
"users": {
"$uid": {
"rateLimit": {
"lastMessage": {
// newData.exists() ensures newData is not null and prevents deleting node
// and $uid === auth.uid ensures the user writing this child node is the owner
".write": "newData.exists() && $uid === auth.uid",
// newData.val() === now ensures the value written is the current timestamp
// to avoid tricking the rules writting false values
// and (!data.exists() || newData.val() > data.val() + 5000)
// ensures no data exists currently in the node. Otherwise it checks if the
// data that will overwrite the node is a value higher than the current timestamp
// plus the value that will rate limit our messages expressed in milliseconds.
// In this case a value of 5000 means that we can only send a message if
// the last message we sent was more than 5 seconds ago
".validate": "newData.val() === now && (!data.exists() || newData.val() > data.val() + 5000)"
}
}
}
},
"messages": {
"$messageId": {
// This rule ensures that we write lastMessage node avoiding just sending the message without
// registering a new timestamp
".write": "newData.parent().parent().child('users').child(auth.uid).child('rateLimit').child('lastMessage').val() === now",
// This rule ensures that we have all the required message fields
".validate": "newData.hasChildren(['timestamp', 'uid', 'message'])",
"uid": {
// This rule ensures that the value written is the id of the message sender
".validate": "newData.val() === auth.uid"
},
"timestamp": {
// This rule ensures that the message timestamp can't be modified
".write": "!data.exists()",
// This rule ensures that the value written is the current timestamp
".validate": "newData.val() === now"
},
"message": {
// This rule ensures that the value written is a string
".validate": "newData.isString()"
},
"$other": {
// This rule ensures that we cant write other fields in the message other than
// the explicitly declared above
".validate": false
}
}
}
}
}
代码实现跨多个位置使用原子写入。如果一个验证失败,则操作不会完成,也不会在数据库中执行任何操作
function sendMessage(message) {
const database = firebase.database();
const pushId = database.ref().child("messages").push().key;
const userId = firebase.auth().currentUser.uid;
const timestampPlaceholder = firebase.database.ServerValue.TIMESTAMP;
let updates = {};
updates["messages/" + pushId] = {
uid: userId,
timestamp: timestampPlaceholder,
message: message,
};
updates[`users/${userId}/rateLimit/lastMessage`] = timestampPlaceholder;
database.ref().update(updates);
}
这对一个仁慈的客户来说是很好的。黑客攻击呢?有人不能发布到最后一条信息引用。然后,他们可以一次又一次地敲打你的引用,并用请求填充它。有没有办法提供速率限制来避免这种情况?实际上,我想我收回了这一点。看起来加藤已经涵盖了这一点。Y您必须发布到最后一条消息,否则写入“消息”将失败。“最后一条消息”"通过要求最后一条消息不少于5秒前来防止死记硬背。非常优雅,这不是最简单的规则,但它们确实完成了任务!有没有办法让未经身份验证的用户能够完成此任务?我猜谁来做并不重要,因此guid可以是关键,而不是$user
在第一个表中。不确定当前的实现是否做到了这一点,但您可能还必须限制重新发送,并可能为每个消息设置一个和一个最小的时间窗口。您觉得这样做对吗?不过,我想问题是,您可能会将消息聚集在一起以通过速率限制……您想得太多了如果你的应用程序需要银行级别的安全性,请编写一个服务器端进程,让它推送消息,添加任何级别的限制。或者为自己节省一些时间,让你的应用程序发布并投入使用。
function sendMessage(message) {
const database = firebase.database();
const pushId = database.ref().child("messages").push().key;
const userId = firebase.auth().currentUser.uid;
const timestampPlaceholder = firebase.database.ServerValue.TIMESTAMP;
let updates = {};
updates["messages/" + pushId] = {
uid: userId,
timestamp: timestampPlaceholder,
message: message,
};
updates[`users/${userId}/rateLimit/lastMessage`] = timestampPlaceholder;
database.ref().update(updates);
}