为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

慕斯709654 2019-12-12 14:10:26

我启动了我的第一个开放式存储库项目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个请求(例如)。


谢谢!


查看完整描述

3 回答

?
慕仰8121524

诀窍是对用户上次发布消息的时间进行审核。然后,您可以根据审核值来强制发布每条消息的时间:


{

  "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 

          }

        }

      } 

  }

}

在这个小提琴中看到它的实际效果。这是小提琴演奏的要点:


var fb = new Firebase(URL);

var userId; // log in and store user.uid here


// run our create routine

createRecord(data, function (recordId, timestamp) {

   console.log('created record ' + recordId + ' at time ' + new Date(timestamp));

});


// updates the last_message/ path and returns the current timestamp

function getTimestamp(next) {

    var ref = fb.child('last_message/' + userId);

    ref.set(Firebase.ServerValue.TIMESTAMP, function (err) {

        if (err) { console.error(err); }

        else {

            ref.once('value', function (snap) {

                next(snap.val());

            });

        }

    });

}


function createRecord(data, next) {

    getTimestamp(function (timestamp) {

        // add the new timestamp to the record data

        var data = {

          sender: userId,

          timestamp: timestamp,

          message: 'hello world'

        };


        var ref = fb.child('messages').push(data, function (err) {

            if (err) { console.error(err); }

            else {

               next(ref.name(), timestamp);

            }

        });

    })

}



查看完整回答
反对 回复 2019-12-13
?
忽然笑

我没有足够的声誉在评论中写信,但我同意Victor的评论。如果将插入fb.child('messages').push(...)循环(即for (let i = 0; i < 100; i++) {...}),则它将成功推送60-80消息(在该500ms窗口框架中)。


受加藤的解决方案启发,我对规则进行了如下修改:


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}返回到下一个回调。那我们只需要ref.update({[key]: data})


该解决方案避免了500毫秒的时间窗口,我们不必担心客户端必须足够快才能在500毫秒内推送消息。如果发送了多个写入请求(垃圾邮件),则它们只能写入中的1个单个密钥messages。可选地,仅创建规则messages可防止这种情况的发生。



查看完整回答
反对 回复 2019-12-13
?
慕标琳琳

现有答案使用两个数据库更新:(1)标记时间戳,以及(2)将标记的时间戳附加到实际写入。Kato的答案需要500毫秒的时间窗口,而ChiNhan的答案则需要记住下一个键。


在单个数据库更新中,有一种更简单的方法来执行此操作。这个想法是使用update()方法一次将多个值写入数据库。安全规则将验证写入的值,以使写入不超过配额。配额被定义为一对值:quotaTimestamp和postCount。postCount是在quotaTimestamp的1分钟内写入的帖子数。如果postCount超过某个值,则安全规则仅拒绝下一次写入。当quotaTimestamp超过1分钟时,将重置postCount。


这是发布新消息的方法:


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

}

安全规则将速率限制为每分钟最多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": {

      ...

    }

  }

}

注意:应保持serverTimeOffset以避免时钟偏斜。


查看完整回答
反对 回复 2019-12-13
  • 3 回答
  • 0 关注
  • 46 浏览
我要回答

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信