Firebase速率限制在安全规则中?

我启动了我的第一个开放式存储库项目EphChat,人们迅速开始向其发送大量请求。


Firebase是否可以对安全规则中的限制请求进行评级?我假设有一种方法可以使用请求时间和先前写入的数据时间,但是在文档中找不到我该如何做的任何事情。


当前的安全规则如下。


{

    "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对象的数据库的写入(和读取?)进行速率限制,因此每秒只能发出1个请求(例如)。


谢谢!


慕斯709654
浏览 602回答 3
3回答

小怪兽爱吃肉

诀窍是对用户上次发布消息的时间进行审核。然后,您可以根据审核值来强制发布每条消息的时间:{&nbsp; "rules": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // this stores the last message I sent so I can throttle them by timestamp&nbsp; &nbsp; &nbsp; "last_message": {&nbsp; &nbsp; &nbsp; &nbsp; "$user": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // timestamp can't be deleted or I could just recreate it to bypass our throttle&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".write": "newData.exists() && auth.uid === $user",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // the new value must be at least 5000 milliseconds after the last (no more than one message every five seconds)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // the new value must be before now (it will be since `now` is when it reaches the server unless I try to cheat)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".validate": "newData.isNumber() && newData.val() === now && (!data.exists() || newData.val() > data.val()+5000)"&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; },&nbsp; &nbsp; &nbsp; "messages": {&nbsp; &nbsp; &nbsp; &nbsp; "$message_id": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // message must have a timestamp attribute and a sender attribute&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".write": "newData.hasChildren(['timestamp', 'sender', 'message'])",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "sender": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".validate": "newData.val() === auth.uid"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "timestamp": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // in order to write a message, I must first make an entry in timestamp_index&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // additionally, that message must be within 500ms of now, which means I can't&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // just re-use the same one over and over, thus, we've effectively required messages&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // to be 5 seconds apart&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".validate": "newData.val() >= now - 500 && newData.val() === data.parent().parent().parent().child('last_message/'+auth.uid).val()"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "message": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".validate": "newData.isString() && newData.val().length < 500"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "$other": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".validate": false&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; }&nbsp;&nbsp; }}在这个小提琴中看到它的实际效果。这是小提琴演奏的要点:var fb = new Firebase(URL);var userId; // log in and store user.uid here// run our create routinecreateRecord(data, function (recordId, timestamp) {&nbsp; &nbsp;console.log('created record ' + recordId + ' at time ' + new Date(timestamp));});// updates the last_message/ path and returns the current timestampfunction getTimestamp(next) {&nbsp; &nbsp; var ref = fb.child('last_message/' + userId);&nbsp; &nbsp; ref.set(Firebase.ServerValue.TIMESTAMP, function (err) {&nbsp; &nbsp; &nbsp; &nbsp; if (err) { console.error(err); }&nbsp; &nbsp; &nbsp; &nbsp; else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ref.once('value', function (snap) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; next(snap.val());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; });}function createRecord(data, next) {&nbsp; &nbsp; getTimestamp(function (timestamp) {&nbsp; &nbsp; &nbsp; &nbsp; // add the new timestamp to the record data&nbsp; &nbsp; &nbsp; &nbsp; var data = {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sender: userId,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; timestamp: timestamp,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message: 'hello world'&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; var ref = fb.child('messages').push(data, function (err) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (err) { console.error(err); }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;next(ref.name(), timestamp);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; })}

慕标琳琳

现有答案使用两个数据库更新:(1)标记时间戳,以及(2)将标记的时间戳附加到实际写入。Kato的答案需要500毫秒的时间窗口,而ChiNhan的答案则需要记住下一个键。在单个数据库更新中,有一种更简单的方法来执行此操作。这个想法是使用update()方法一次将多个值写入数据库。安全规则将验证写入的值,以使写入不超过配额。配额被定义为一对值:quotaTimestamp和postCount。postCount是在quotaTimestamp的1分钟内写入的帖子数。如果postCount超过某个值,则安全规则仅拒绝下一次写入。当quotaTimestamp超过1分钟时,将重置postCount。这是发布新消息的方法:function postMessage(user, message) {&nbsp; const now = Date.now() + serverTimeOffset;&nbsp; if (!user.quotaTimestamp || user.quotaTimestamp + 60 * 1000 < now) {&nbsp; &nbsp; // Resets the quota when 1 minute has elapsed since the quotaTimestamp.&nbsp; &nbsp; user.quotaTimestamp = database.ServerValue.TIMESTAMP;&nbsp; &nbsp; user.postCount = 0;&nbsp; }&nbsp; user.postCount++;&nbsp; const values = {};&nbsp; const messageId = // generate unique id&nbsp; values[`users/${user.uid}/quotaTimestamp`] = user.quotaTimestamp;&nbsp; values[`users/${user.uid}/postCount`] = user.postCount;&nbsp; values[`messages/${messageId}`] = {&nbsp; &nbsp; sender: ...,&nbsp; &nbsp; message: ...,&nbsp; &nbsp; ...&nbsp; };&nbsp; return this.db.database.ref().update(values);}安全规则将速率限制为每分钟最多5个帖子:{&nbsp; "rules": {&nbsp; &nbsp; "users": {&nbsp; &nbsp; &nbsp; "$uid": {&nbsp; &nbsp; &nbsp; &nbsp; ".read": "$uid === auth.uid",&nbsp; &nbsp; &nbsp; &nbsp; ".write": "$uid === auth.uid && newData.child('postCount').val() <= 5",&nbsp; &nbsp; &nbsp; &nbsp; "quotaTimestamp": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Only allow updating quotaTimestamp if it's staler than 1 minute.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".validate": "&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; newData.isNumber()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && (newData.val() === now&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ? (data.val() + 60 * 1000 < now)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : (data.val() == newData.val()))"&nbsp; &nbsp; &nbsp; &nbsp; },&nbsp; &nbsp; &nbsp; &nbsp; "postCount": {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Only allow postCount to be incremented by 1&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // or reset to 1 when the quotaTimestamp is being refreshed.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ".validate": "&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; newData.isNumber()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && (data.exists()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ? (data.val() + 1 === newData.val()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; || (newData.val() === 1&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && newData.parent().child('quotaTimestamp').val() === now))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : (newData.val() === 1))"&nbsp; &nbsp; &nbsp; &nbsp; },&nbsp; &nbsp; &nbsp; &nbsp; "$other": { ".validate": false }&nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; },&nbsp; &nbsp; "messages": {&nbsp; &nbsp; &nbsp; ...&nbsp; &nbsp; }&nbsp; }}注意:应保持serverTimeOffset以避免时钟偏斜。
打开App,查看更多内容
随时随地看视频慕课网APP