我们正在提供人们将在其网站上嵌入的服务,我们希望使用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是一个试图测试这个想法的代码。
答案 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对限制代码的改编似乎是一个很好的起点。还有一些令人烦恼的漏洞,比如强迫计数器更新,从柜台中获取可量化的指标/分析(这需要通过某种方式挂钩到统计工具,并且对于准确的计费报告和客户查询是必要的),以及也能够准确地确定访问何时“结束”。
根据贾斯汀最初的想法,我认为通过简化客户负责的数量可以省略很多这种开销。也许是这样的:
从这个基础开始,我将调整安全规则和结构如下:
{
"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页面)上运行它。