使用安全规则限制子/字段访问

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”会在记录被批准的情况下授予访问权限;因此,只要获得批准,phonestate 中的 .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 观点的例子:聊天室希望允许用户查看在joinDateleaveDate 之间发送的消息。我们可以在查询中进行过滤以用于显示目的,但最终用户仍然可以访问他们不应该看到的消息。由于所有日期都是动态的,我们不能闯入approved_listpending_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安全规则中的特定限制?

服务器安全检测提示“关闭远程访问或限制访问的ip地址” 怎么关闭呢?阿里云的服务器

腾讯云设置安全组

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

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

访问限制