【问题标题】:Using Firebase Security Rules, how can I secure a node that has transactions run on it?使用 Firebase 安全规则,如何保护运行交易的节点?
【发布时间】:2021-02-24 19:29:22
【问题描述】:

在我的数据库中,我有一个用户节点,其中包含用户 ID 下的数据。 这包括他们的个人简介、关注者数量、用户是否是版主等等。

users
{
    userId1
    {
        bio: "Example bio..."
        followers: 250
        moderator: true
        ...
    }
}

为了使关注者的数量正确,我使用transaction block 在每次点击关注按钮时增加关注者属性。还有一些其他属性也需要事务块。

不幸的是,我发现为了使交易正常工作,$userId 节点的安全规则必须设置为:“.write”: “auth != null”。否则,当有人点击关注按钮时,关注者属性的数量不会增加。因为事务块查询整个用户,所以我们不能将安全规则限制为“followers”属性。

"users":
{
     "$userId": 
     {
        // Has to be set like this or transactions won't work
        ".read": "auth != null",
        ".write": "auth != null",


        "bio":
        {
            // This will have no effect due to rule cascading
            ".write": "auth.uid === $userId"
        }

        "moderator":
        {
            // This will have no effect due to rule cascading
            ".write": ...
        }
    }
}

而且由于规则级联,这使得为用户下的任何其他属性设置特定规则似乎是不可能的,包括 bio 以及用户是否是版主等。这使得用户属性容易受到恶意用户的更改。

同样的事情发生在帖子和点赞上,Firebase 文档中使用的示例。因为事务块查询整个帖子,我们不能将安全规则限制为“likes”属性。由于级联,所有其他帖子属性都必须适应“.write”: “auth !=null” 设置。

我能做的最好的事情就是使用验证,但这不会阻止恶意用户将他们的关注计数设置为 10,000 或在他们以某种方式获得访问权限时让自己成为版主。

使用 Firebase 规则,有什么方法可以保护运行交易的节点?


编辑:更多信息

这是我的交易块增加关注者数量的简化版本:

    // Run transaction block on the user in the "users" node
    userRef.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in

        // Store the user 
        if var user = currentData.value as? [String: AnyObject]
        {
            // Get the number of followers
            var numberOfFollowers = user["numberOfFollowers"] as? Int ?? 0

            // Increase the number of followers by 1
            numberOfFollowers += 1


            // Set the new number of followers
            user["numberOfFollowers"] = numberOfFollowers as AnyObject?

            // Set the user value and report transaction success
            currentData.value = user

            return TransactionResult.success(withValue: currentData)
        }

        return TransactionResult.success(withValue: currentData)
    })

这是我的数据库中存储关注者的方式:

myDatabase: {
    followers: {
        "andrew098239101": {
            // These are all the user ID's of users that follow "andrew098239101"
            "robert12988311": true
            "sarah9234298347": true
            "alex29101922": true
            "greg923749232": true
        }
        "robert12988311": {
            "alex29101922": true
        }
    }
    ...
    users: {
        "andrew098239101": {
             // Andrew's user info
             "bio": "hello I am Andrew"
             "numberOfFollowers": 4
             "moderator": true
             ...
        }
        "robert12988311": {
             "bio": "I'm Robert"
             "numberOfFollowers": 1
             "moderator": false
             ...
        }
    }
}

有一个类似的节点用于关注等

【问题讨论】:

  • “每次点击关注按钮时,我都会使用事务块来增加关注者属性”我实际上建议使用ServerValue.increment(1),因为它将是more efficient。不过,它不应该对您询问的安全规则产生影响,因为它仍然是同一个用户编写的。
  • 谢谢,我试试 ServerValue.increment(1) 看看有没有效果
  • 您能否确保您发布的数据结构是有效的 JSON?无法存储您在 Firebase 实时数据库中发布的最后一个结构,而实际结构对于如何保护它(以及这是否可能)很重要。
  • 感谢您更新您的答案,我更新了数据库示例以更清晰。

标签: swift firebase firebase-realtime-database firebase-authentication firebase-security


【解决方案1】:

您应该只能在 followers 属性上设置较宽松的写入权限。所以是这样的:

"users": {
     "$userId":  {
        ".read": "auth != null",
        ".write": "auth.uid === $userId",
        "followers": {
            ".write": "auth.uid !== null"
        },
    }
}

这样,所有经过身份验证的用户都可以写入/users/$uid/followers,但只有配置文件的所有者可以写入其他值。


我建议将 followers 与其他用户配置文件数据分开,这样您就有两个顶级列表:users/$uid 包含只有所有者才能写入的用户配置文件,followers/$uid 包含数据追随者可以写。


我还建议不仅存储计数,还存储关注者的 UID。事实上,我会使用这种结构:

users: {
  "uidOfMichael": {
    followers: {
      "uidOfFrank": true,
      "uidOfJames": true,
      "uidOfAndrew": true
    },
    followerCount: 3
  },
  "uidOfFrank": {
    followers: {
      "uidOfJames": true,
      "uidOfAndrew": true
    },
    followerCount: 2
  },
  "uidOfJames": {
    followers: {
      "uidOfAndrew": true
    },
    followerCount: 1
  },
  "uidOfAndrew": {
    followers: {
      "uidOfJames": true
    },
    followerCount: 1
  }
}

现在您可以验证这些额外的东西:

  1. 用户只能将自己的 UID 写入followers
  2. 用户只能在将其 UID 添加到 followers 时增加 followerCount

规则类似于(可能有错别字):

"users": {
     "$userId":  {
        ".read": "auth != null",
        ".write": "auth.uid === $userId",
        "followers": {
            "$followerId": {
                ".write": "auth.uid !== $followerId"
            }
        },
        "followerCount": {
            ".write": "
              newData.val() === data.val() + 1 &&
              !data.parent().child('followers').child(auth.uid).exists() &&
              newData.parent().child('followers').child(auth.uid).exists()
            "
        }
    }
}

【讨论】:

  • 嗨弗兰克,感谢您抽出宝贵的时间来回答。不幸的是,第一个使用 ".write": "auth.uid === $userId" 的例子不起作用,因为它不是运行事务块的用户自己的 ID,而是被跟踪的另一个用户的 ID。我有一个单独的“追随者”节点来跟踪追随者 ID。再次感谢,我会尝试根据您的回答找到解决方案,并使用 ServerValue.increment(1) 看看它是否有所作为。
  • 如果您能显示交易代码,那将非常有帮助。但是在第一个/当前数据模型中,事务应该在可以完成工作的最低节点上运行——那里似乎是followers
  • 我的数据库中的“followers”节点没有嵌套在“users”中,而是它自己在数据库中的节点。用户节点所做的只是跟踪关注者(和关注者)的数量,而关注者的用户 ID 保存在另一个节点中。我可以编辑原始帖子以提供更多信息
  • 是的,这意味着您需要将整个数据库锁定在事务中,因为(目前)实时数据库事务必须在单个公共根节点上运行。在这种情况下,最好不要使用事务,而是使用多路径更新——然后通过让用户再次单击“喜欢”来处理安全规则的拒绝。或者,您可以更改数据模型以更准确地反映我在回答中的内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 2017-08-03
  • 1970-01-01
  • 1970-01-01
  • 2021-12-19
相关资源
最近更新 更多