Google / Firebase云功能的速率限制?

时间:2018-06-07 13:01:46

标签: node.js firebase express google-cloud-functions rate-limiting

我正在使用Firebase开发一个在内部使用云功能作为REST API的应用。我的问题是,是否有一种简单的方法来实现与slack uses类似的每IP /每用户速率限制,除了每IP 每个用户基础,而不是每个应用程序(因为它是所有应用程序)。也可以选择支持小型爆发。

示例代码(请参阅// TODO:条评论):

exports.myCoolFunction = functions.https.onRequest((req, res) => {
        // TODO: implement IP rate-limiting here
        unpackToken(req).then((token) => { // unpackToken resolves with a response similar to verifyIdToken based on the "Authorization" header contents
                // TODO: implement user-based rate-limiting here (based on token.uid)
                if (!req.body) return res.status(400).end();
                if (typeof req.body.name !== "string") return res.status(400).end();
                if (typeof req.body.user !== "string") return res.status(400).end();

                // more input sanitization and function logic here

                return res.status(501).end(); // fallback in all requests, do not remove
        }).catch(() => res.status(403).end());
});

如果超过速率限制,我想简单地使用529 Too Many Requests状态代码终止请求。这是为了防止应用程序错误泛滥网络并防止滥用REST API。

  

这应考虑到Firebase启动/关闭服务器实例并同时运行多个实例。

     

我也在使用Firestore数据库,如有必要,可以使用旧版实时数据库。

2 个答案:

答案 0 :(得分:2)

基于每个用户执行此操作听起来相当简单:

  1. 将每个请求传递给用户的ID令牌。
  2. 解码云功能中的ID令牌以确定UID。有关前两个步骤的示例,请参阅the functions-samples repo
  3. 将用户UID调用该函数的事实推送到数据库,可能会将其添加到列表中。例如。 admin.database().ref(`/userCalls/$uid`).push(ServerValue.TIMESTAMP)
  4. 使用admin.database().ref(`/userCalls/$uid`).orderByKey().startAt(Date.now()-60000)
  5. 等查询最近通话次数
  6. 计算结果,如果结果太高则拒绝。
  7. 我不确定调用方的IP地址是否传递给云功能。如果是,您可以对IP地址执行相同的逻辑。如果它没有通过,那么很难以一种不容易欺骗的方式对其进行限速。

答案 1 :(得分:2)

我制作了一个库,用于限制对Firebase函数的调用: firebase-functions-rate-limiter 该库使用realtimeDB或firestore(可配置)作为后端。它以Frank所描述的类似方法存储数据,但是更经济。它不使用集合,而是使用单个文档,每个限定符均带有数组(例如,用户ID)。这意味着超出的呼叫只有一次读取,而允许的呼叫只有读写。

$ npm i --save firebase-functions-rate-limiter

这里是一个例子:

import * as admin from "firebase-admin";
import * as functions from "firebase-functions";
import { FirebaseFunctionsRateLimiter } from "firebase-functions-rate-limiter";

admin.initializeApp(functions.config().firebase);
const database = admin.database();

const limiter = FirebaseFunctionsRateLimiter.withRealtimeDbBackend(
    {
        name: "rate_limiter_collection",
        maxCalls: 2,
        periodSeconds: 15,
    },
    database,
);
exports.testRateLimiter = 
  functions.https.onRequest(async (req, res) => {
    await limiter.rejectOnQuotaExceeded(); // will throw HttpsException with proper warning

    res.send("Function called");
});