使用安全规则限制子/字段访问
Posted
技术标签:
【中文标题】使用安全规则限制子/字段访问【英文标题】:Restricting child/field access with security rules 【发布时间】:2012-12-27 02:59:55 【问题描述】:我正在编写一个应用程序,允许用户提交提名,这些提名在显示给其他用户之前会经过审核。这需要一些我迄今为止未能成功实施安全规则的限制:
-
隐藏所有尚未获得批准的提名
隐藏提交时的私有字段(电话、审批状态、创建日期等)
我目前的规则如下:
"rules":
"nominations":
".read": true,
"$nominationId":
".read": "data.child('state').val() == 'approved' || auth != null", // Only read approved nominations if not authenticated
".write": "!data.exists()", // Only allow new nominations to be created
"phone":
".read": "auth != null" // Only allow authenticated users to read phone number
,
"state":
".read": "auth != null", // Only allow authenticated users to read approval state
".write": "auth != null" // Only allow authenticated users to change state
子规则(例如$nomination
)不会阻止从父级读取整个子级。如果我在https://my.firebaseio.com/nominations 上收听child_added
,即使有上述安全规则,它也会愉快地返回所有孩子及其所有数据。
我目前的解决方法是保留一个名为 approved
的单独节点,并在有人批准或拒绝提名时简单地在列表之间移动数据,但这似乎是一种非常糟糕的方法。
更新
根据Michael Lehenbauer 的出色评论,我以最小的努力重新实现了最初的想法。
新的数据结构如下:
my-firebase
|
`- nominations
|
`- entries
| |
| `- private
| `- public
|
`- status
|
`- pending
`- approved
`- rejected
每个提名都存储在entries
下,private
下存储私人数据,例如电话号码、电子邮件等,public
下可公开查看数据。
更新规则如下:
"rules":
"nominations":
"entries":
"$id":
".write": "!data.exists()",
"public":
".read": true,
,
"private":
".read": "auth != null"
,
"status":
"pending":
".read": "auth != null",
"$id":
".write": "root.child('nominations/entries').child($id).exists() && (auth != null || newData.val() == true)"
,
"approved":
".read": true,
"$id":
".write": "root.child('nominations/entries').child($id).exists() && auth != null"
,
"rejected":
".read": "auth != null",
"$id":
".write": "root.child('nominations/entries').child($id).exists() && auth != null"
以及 javascript 实现:
var db = new Firebase('https://my.firebaseio.com')
var nominations = db.child('nominations')
var entries = nominations.child('entries')
var status = nominations.child('status')
var pending = status.child('pending')
var approved = status.child('approved')
var rejected = status.child('rejected')
// Create nomination via form input (not shown)
var createNomination = function()
var data =
public:
name: 'Foo',
age: 20
,
private:
createdAt: new Date().getTime(),
phone: 123456
var nomination = entries.push()
nomination.setWithPriority(data, data.private.createdAt)
pending.child(nomination.name()).set(true)
// Retrieve current nomination status
var getStatus = function(id, callback)
approved.child(id).once('value', function(snapshot)
if (snapshot.val())
callback(id, 'approved')
else
rejected.child(id).once('value', function(snapshot)
callback(id, snapshot.val() ? 'rejected' : 'pending')
)
)
// Change status of nomination
var changeStatus = function(id, from, to)
status.child(from).child(id).remove()
status.child(to).child(id).set(true)
我正在努力解决的唯一部分是处理状态更改,我目前的方法肯定可以改进:
_.each([pending, approved, rejected], function(status)
status.on('child_added', function(snapshot)
$('#' + snapshot.name()).removeClass('pending approved rejected').addClass(status.name())
)
)
我计划在nominations/status
上使用child_changed
,但我无法让它可靠地工作。
【问题讨论】:
【参考方案1】:如果我完全理解安全规则的工作方式(我只是自己学习它们),那么当任何一条规则允许访问时,都会授予访问权限。因此,它们被解读为:
提名“.read”:正确,已授予访问权限 其他规则:未读此外,如果删除该规则,$nominationId
“.read”会在记录被批准的情况下授予访问权限;因此,只要获得批准,phone
和 state
中的 .read
就会变得多余。
将其分解为public/
和private/
子级可能是最简单的,如下所示:
nominations/unapproved/ # only visible to logged in users
nominations/approved/ # visible to anyone (move record here after approval)
nominations/approved/public/ # things everyone can see
nominations/approved/restricted/ # things like phone number, which are restricted
更新
仔细考虑一下,我认为您仍然会遇到将approved/
设为公开的问题,这将允许您列出记录,并将approved/restricted/
设为私有。在这个用例中,受限数据可能也需要自己的路径。
【讨论】:
【参考方案2】:加藤是对的。重要的是要了解安全规则从不过滤数据。对于任何位置,您要么能够读取所有数据(包括其子数据),要么无法读取。因此,就您的规则而言,在“提名”下具有 ".read": true 会否定您的所有其他规则。
所以我在这里推荐的方法是有 3 个列表。一个包含提名数据,一个包含已批准的提名列表,一个包含待定提名列表。
你的规则可能是这样的:
"rules":
// The actual nominations. Each will be stored with a unique ID.
"nominations":
"$id":
".write": "!data.exists()", // anybody can create new nominations, but not overwrite existing ones.
"public_data":
".read": true // everybody can read the public data.
,
"phone":
".read": "auth != null", // only authenticated users can read the phone number.
,
"approved_list":
".read": true, // everybody can read the approved nominations list.
"$id":
// Authenticated users can add the id of a nomination to the approved list
// by creating a child with the nomination id as the name and true as the value.
".write": "auth != null && root.child('nominations').child($id).exists() && newData.val() == true"
,
"pending_list":
".read": "auth != null", // Only authenticated users can read the pending list.
"$id":
// Any user can add a nomination to the pending list, to be moderated by
// an authenticated user (who can then delete it from this list).
".write": "root.child('nominations').child($id).exists() && (newData.val() == true || auth != null)"
未经身份验证的用户可以通过以下方式添加新提名:
var id = ref.child('nominations').push( public_data: "whatever", phone: "555-1234" );
ref.child('pending_list').child(id).set(true);
经过身份验证的用户可以通过以下方式批准消息:
ref.child('pending_list').child(id).remove();
ref.child('approved_list').child(id).set(true);
要呈现已批准和待处理的列表,您需要使用类似以下代码:
ref.child('approved_list').on('child_added', function(childSnapshot)
var nominationId = childSnapshot.name();
ref.child('nominations').child(nominationId).child('public_data').on('value', function(nominationDataSnap)
console.log(nominationDataSnap.val());
);
);
这样,您可以将approved_list 和pending_list 用作可以枚举的轻量级列表(分别由未经身份验证和经过身份验证的用户),并将所有实际提名数据存储在提名列表中(没有人可以直接枚举)。
【讨论】:
在我看来,任何需要过滤或隐藏最终用户信息的应用程序都会遇到类似的问题,坦率地说,您概述的方法只是证明我可能应该重新考虑使用 Firebase具有此类要求的项目。 很公平。虽然我会记住几件事。首先,像这样对数据进行非规范化起初可能会让人感到尴尬,但这是一种完全有效的方法。例如,当您查看 Twitter 提要时,Twitter 不会进行任何“过滤”。这一切都是提前预先计算好的。这会使用额外的空间,并可能导致临时不一致的数据,但它的扩展性比加入/过滤数据要好得多。其次,您要寻找的基本上是每行访问控制,我认为这在任何数据库中都很难找到。如果我能以任何方式提供帮助,请随时联系(firebase dot com 的迈克尔)! 举一个@SimenBrekken 观点的例子:聊天室希望允许用户查看在joinDate
和leaveDate
之间发送的消息。我们可以在查询中进行过滤以用于显示目的,但最终用户仍然可以访问他们不应该看到的消息。由于所有日期都是动态的,我们不能闯入approved_list
、pending_list
等。@michael-lehenbauer,是否可以使用 Firebase 功能来保护数据访问控制?我在 FB 文档中没有看到 onRead()
@MichaelLehenbauer 其次,您要寻找的基本上是每行访问控制,我认为这在任何数据库中都很难找到。具有讽刺意味的是,这通常很难(或不完整)在许多数据库中,因为用户登录并不总是 1:1 映射到数据库登录。有了firebase,这个问题就解决了。简单的一点应该是“仅显示./user_id
字段与登录用户匹配的记录”,或“仅显示登录用户组有权访问该记录的记录”。【参考方案3】:
这个帖子有点过时了,它可能有一个通过规则的解决方案,但正如视频所说,它是一个巧妙的技巧: https://youtu.be/5hYMDfDoHpI?t=8m50s
这可能不是一个好习惯,因为 firebase 文档说规则不是过滤器: https://firebase.google.com/docs/database/security/securing-data
我不是安全专家,但我测试了这个技巧,它对我来说效果很好。 :)
所以我希望能更好地理解这个实现的安全问题。
【讨论】:
最好把答案放在这里,而不是可能死掉的链接... 上面的视频没有违反“规则不是过滤器”的声明——也就是说,它不是一个试图绕过它的技巧。这只是一种结构化数据的方式,可以有效地与规则系统配合使用。以上是关于使用安全规则限制子/字段访问的主要内容,如果未能解决你的问题,请参考以下文章
如何检查传入号码数据是否大于或小于Firestore安全规则中的特定限制?