You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为用户与商家的Firebase实时消息配置安全规则

Firebase Realtime Database 消息规则问题解决

问题背景

  • 普通用户可升级为Pro用户,最多创建2个商家身份,数据存储在Users表中
  • 消息仅允许在普通用户与商家身份之间互发,商家身份标识格式为uid_businessId,普通用户标识为纯uid
  • 消息需双向存储在双方的消息路径下,当前存在两个问题:商家身份发消息时只能写入自身路径,无法写入收件方路径;Firebase持续提示数据库存在滥用风险

数据结构示例

Users表结构

Users
  |-- user uid (例如: Wqnxj20ZosOTkfe4D7dwQc9iygp2)
  |     |-- userBusiness1Id : (例如: 10553)
  |     |-- userBusiness2Id : (例如: 10552)
  |     |-- userUid : (例如: Wqnxj20ZosOTkfe4D7dwQc9iygp2)
  |
  |-- user uid (例如: v6HBc00RL1RQdysCsc09mhKdItD2)
        |-- userUid : (例如: v6HBc00RL1RQdysCsc09mhKdItD2)

用户标识示例

let businessUserId = "Wqnxj20ZosOTkfe4D7dwQc9iygp2_10553"
let standardUserId = "v6HBc00RL1RQdysCsc09mhKdItD2"

User-Messages表结构

User-Messages
  |-- businessUserId
  |     |-- standardUserId
  |           |-- chatLogId
  |               |-- fromId: standardUserId
  |               |-- toId: businessUserId
  |
  |-- standardUserId
        |-- businessUserId
              |-- chatLogId
                  |-- fromId: standardUserId
                  |-- toId: businessUserId

现有消息写入代码

func sendMessage(text: String, fromId: String, toId: String) {
    let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
    if trimmedText.isEmpty { return }
    
    let timeStamp = Int64(Date().timeIntervalSince1970)
    
    let reference = Database.database().reference().child("User-Messages").child(fromId).child(toId).childByAutoId()
    let toReference = Database.database().reference().child("User-Messages").child(toId).child(fromId).childByAutoId()
    
    let chatModel = ChatModel(chatId: reference.key, text: trimmedText, fromId: fromId, toId: toId, timestamp: timeStamp, messageIsRead: true)
    let chatModelForRecipient = ChatModel(chatId: toReference.key, text: trimmedText, fromId: fromId, toId: toId, timestamp: timeStamp, messageIsRead: false)
    
    reference.setValue(chatModel.toDictionary())
    toReference.setValue(chatModelForRecipient.toDictionary())
}

当前规则存在的问题

现有规则无法支持商家身份写入收件方路径,且缺少数据校验导致安全风险:

"User-Messages": {
  "$userId": {
    ".read": "auth != null && (
      auth.uid === $userId || 
      auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness1Id').val() === $userId ||
      auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness2Id').val() === $userId
    )",
    "$recipientId": {
      ".write": "auth != null && (
        auth.uid === $userId || // Sender can write to their own path
        auth.uid === $recipientId || // Sender can write to the recipient's path
        auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness1Id').val() === $userId || // Business1 sender
        auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness2Id').val() === $userId || // Business2 sender
        auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness1Id').val() === $recipientId || // Business1 recipient
        auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness2Id').val() === $recipientId // Business2 recipient
      )"
    }
  }
},

修正后的安全规则

"User-Messages": {
  "$userPathId": {
    // 仅允许用户本人或其商家身份读取自己的消息路径
    ".read": "auth != null && (
      auth.uid === $userPathId ||
      (auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness1Id').val()) === $userPathId ||
      (auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness2Id').val()) === $userPathId
    )",
    "$recipientPathId": {
      ".write": "auth != null && (
        // 情况1:发送方是普通用户
        (
          newData.child('fromId').val() === auth.uid &&
          // 验证写入路径是发送方→收件方 或 收件方→发送方的合法路径
          ($userPathId === auth.uid && $recipientPathId === newData.child('toId').val()) ||
          ($userPathId === newData.child('toId').val() && $recipientPathId === auth.uid) &&
          // 确保消息在普通用户和商家之间互发
          (newData.child('toId').val().contains('_') || newData.child('fromId').val().contains('_'))
        ) ||
        // 情况2:发送方是商家身份
        (
          newData.child('fromId').val().startsWith(auth.uid + '_') &&
          // 验证该商家身份属于当前认证用户
          (
            newData.child('fromId').val() === auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness1Id').val() ||
            newData.child('fromId').val() === auth.uid + '_' + root.child('Users').child(auth.uid).child('userBusiness2Id').val()
          ) &&
          // 验证写入路径是商家→普通用户 或 普通用户→商家的合法路径
          ($userPathId === newData.child('fromId').val() && $recipientPathId === newData.child('toId').val()) ||
          ($userPathId === newData.child('toId').val() && $recipientPathId === newData.child('fromId').val()) &&
          // 确保收件方是普通用户
          !newData.child('toId').val().contains('_')
        )
      )",
      // 校验消息数据结构完整性,防止恶意数据写入
      ".validate": "newData.hasChildren(['fromId', 'toId', 'text', 'timestamp', 'messageIsRead']) &&
        newData.child('fromId').isString() &&
        newData.child('toId').isString() &&
        newData.child('text').isString() &&
        newData.child('timestamp').isNumber() &&
        newData.child('messageIsRead').isBoolean()"
    }
  }
}

规则说明

  1. 读取规则:保留原有逻辑,仅允许用户访问自己的普通身份或商家身份对应的消息路径。
  2. 写入规则:
    • 分普通用户、商家身份两种场景验证发送方权限
    • 商家身份发送时,先校验该商家ID属于当前认证用户
    • 强制消息只能在普通用户与商家身份之间互发,禁止同类型身份间发消息
    • 验证写入路径与消息中的fromId/toId完全匹配,避免非法路径写入
  3. 数据校验:添加.validate规则,确保消息包含所有必填字段且类型正确,降低数据库滥用风险。

内容的提问来源于stack exchange,提问作者S.S.S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 16:10:19