如何为用户与商家的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()" } } }
规则说明
- 读取规则:保留原有逻辑,仅允许用户访问自己的普通身份或商家身份对应的消息路径。
- 写入规则:
- 分普通用户、商家身份两种场景验证发送方权限
- 商家身份发送时,先校验该商家ID属于当前认证用户
- 强制消息只能在普通用户与商家身份之间互发,禁止同类型身份间发消息
- 验证写入路径与消息中的
fromId/toId完全匹配,避免非法路径写入
- 数据校验:添加
.validate规则,确保消息包含所有必填字段且类型正确,降低数据库滥用风险。
内容的提问来源于stack exchange,提问作者S.S.S
相关产品推荐
相关产品推荐

