Firebase安全规则,以防止客户绕过使用情况跟踪

时间:2014-10-23 03:24:28

标签: firebase firebase-security

我们正在提供人们将在其网站上嵌入的服务,我们希望使用Firebase作为我们的后端。我们希望将订阅率基于页面浏览量或类似内容。现在我们难以理解如何阻止客户缓存我们的客户端js代码并省略任何试图增加页面查看计数器的部分。

我们需要做的是创建一个安全规则,以原子方式阻止某人从一个位置读取,除非他们在另一个位置增加了计数器。关于如何做到这一点的任何想法?

例如,假设以下架构:

{
  "comments" : {
    "-JYlV8KQGkUk18-nnyHk" : {
      "content" : "This is the first comment."
    },
    "-JYlV8KWNlFZHLbOphFO" : {
      "content" : "This is a reply to the first.",
      "replyToCommentId" : "-JYlV8KQGkUk18-nnyHk"
    },
    "-JYlV8KbT63wL9Sb0QvT" : {
      "content" : "This is a reply to the second.",
      "replyToCommentId" : "-JYlV8KWNlFZHLbOphFO"
    },
    "-JYlV8KelTmBr7uRK08y" : {
      "content" : "This is another reply to the first.",
      "replyToCommentId" : "-JYlV8KQGkUk18-nnyHk"
    }
  },
  oldPageViews: 32498,
  pageViews: 32498
}

如果客户端首次递增pageViews字段,那么只允许对注释进行读取访问的方法是什么?起初我正在考虑有两个字段(类似于pageViews和oldPageViews),并开始通过递增pageViews,读取注释,然后递增oldPageViews以匹配,并且只允许读取注释,如果pageViews === oldPageViews + 1.但是,除非可以原子方式完成,否则如果客户启动流程但数据未完成,数据可能会进入损坏状态。

Here是一个试图测试这个想法的代码。

2 个答案:

答案 0 :(得分:2)

我建议改变加藤的限速答案:https://stackoverflow.com/a/24841859/75644

数据:

{
  "comments": {
    "-JYlV8KQGkUk18-nnyHk": {
      "content": "This is the first comment."
    },
    "-JYlV8KWNlFZHLbOphFO": {
      "content": "This is a reply to the first.",
      "replyToCommentId": "-JYlV8KQGkUk18-nnyHk"
    },
    "-JYlV8KbT63wL9Sb0QvT": {
      "content": "This is a reply to the second.",
      "replyToCommentId": "-JYlV8KWNlFZHLbOphFO"
    },
    "-JYlV8KelTmBr7uRK08y": {
      "content": "This is another reply to the first.",
      "replyToCommentId": "-JYlV8KQGkUk18-nnyHk"
    },
    "timestamp" : 1413555509137
  },
  "pageViews" : {
    "count" : 345030,
    "lastTs" : 1413555509137
  }
}

安全规则:

{
  "rules": {
    "pageViews": {
      ".validate": "newData.hasChildren(['count','lastTs'])",
      "count": {
        ".validate": "newData.exists() && newData.isNumber() && newData.val() > data.val()"
      },
      "lastTs": {
        // timestamp can't be deleted or I could just recreate it to bypass our throttle
        ".write": "newData.exists()",
          // the new value must be at least 500 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()+500)"
      }
    },
    "comments": {
      // The comments can't be read unless the pageViews lastTs value is within 500 milliseconds of now
      ".read": "root.child('pageViews').child('lastTs').val() > now - 501",
      ".write": true
    }
  }
}

注意:我没有对此进行过测试,因此您需要稍微调试一下它是否有效。

另外,根据你的样本数据,我没有处理uid的问题。您需要确保管理谁可以在这里读/写。

答案 1 :(得分:1)

Justin对限制代码的改编似乎是一个很好的起点。还有一些令人烦恼的漏洞,比如强迫计数器更新,从柜台中获取可量化的指标/分析(这需要通过某种方式挂钩到统计工具,并且对于准确的计费报告和客户查询是必要的),以及也能够准确地确定访问何时“结束”。

根据贾斯汀最初的想法,我认为通过简化客户负责的数量可以省略很多这种开销。也许是这样的:

  1. 仅强制用户更新时间戳计数器
  2. 使用node.js脚本来监视计数器的更新
  3. 让node.js脚本“存储”审计数据,最好是 将其发送到keen.io,intercom.io等分析工具
  4. 从这个基础开始,我将调整安全规则和结构如下:

    {
      "rules": {
        "count": {
          // updated only from node.js script
          // assumes our node worker authenticates with a special uid we created 
          // http://jsfiddle.net/firebase/XDXu5/embedded/result/
          ".write": "auth.uid === 'ADMIN_WORKER'", 
          ".validate": "newData.exists() && newData.isNumber() && newData.val() > data.val()"
        },
        "lastTs": {
          // timestamp can't be deleted or I could just recreate it to bypass our throttle
          ".write": "newData.exists()",
            // the new value must be equal to now (i.e. Firebase.ServerValue.TIMESTAMP)
          ".validate": "newData.isNumber() && newData.val() === now"
        },
        "comments": {
          // The comments can't be read unless the pageViews lastTs value is within 30 seconds
          ".read": "root.child('pageViews').child('lastTs').val() > now - 30000",
          "$comment": {
            ".write": "???"     
          }
        }
      }
    }
    

    现在我将编写一个简单的节点脚本来执行计数和管理任务:

    var Firebase = require('firebase');
    var ref = new Firebase(URL);
    ref.child('lastTs').on('value', heartbeatReceived);
    var lastCheck = null;
    
    function heartbeatReceived(snap) {
      if( isNewSession(snap.val()) ) {
        incrementCounter();
      }
      updateStatsEngine(snap);
    }
    
    function incrementCounter() {
      ref.child('count').transaction(function(currVal) {
        return (currVal||0) + 1;
      });
    }
    
    function isNewSession(timestamp) {
      // the criteria here is pretty arbitrary and up to you, maybe
      // something like < 30 minutes since last update or the same day?
      var res = lastCheck === null || timestamp - lastCheck > 30 * 60 * 1000;
      lastCheck = timestamp;
      return res;
    }
    
    function updateStatsEngine(snap) {
      // contact keen.io via their REST API
      // tell intercom.io that we have an event
      // do whatever is desired to store quantifiable stats
      // and track billing info
      //
      //var client = require('keen.io').configure({
      //    projectId: "<project_id>",
      //    writeKey: "<write_key>",
      //    readKey: "<read_key>",
      //    masterKey: "<master_key>"
      //});
      //
      //client.addEvent("collection", {/* data */});
    }
    

    这种方法的缺点是,如果我的管理脚本发生故障,那么在此期间的任何事件都不会被记录。然而,这个脚本的奇妙之处在于它的简单性。

    它不会有很多错误。添加monit,upstart或其他工具以确保它保持运行并且不会崩溃。完成工作。

    它也非常通用。我可以在我的笔记本电脑甚至我的Android手机(作为HTML页面)上运行它。