Cloud Firestore权限异常:一对一聊天读写权限拒绝问题排查
问题描述
我正在实现支持聊天列表拉取与实时一对一聊天的功能,为限制数据访问,配置了Cloud Firestore规则,要求仅登录用户可读写与自身uid相关的数据。但拉取chatlist数据及发送消息时,均触发错误:
Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions
原规则代码
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /messages/{document=**} { allow read: if request.auth != null && (request.auth.uid == resource.data.uid || request.auth.uid == resource.data.recipientId); } match /messages/{document} { allow write: if request.auth != null && (request.auth.uid == resource.data.uid || request.auth.uid == resource.data.recipientId); } // Chatlist Collection match /chatlist/{document=**} { allow read: if request.auth != null && (request.auth.uid == resource.data.uid || request.auth.uid == resource.data.recipientId); allow write: if request.auth != null && (request.auth.uid == resource.data.uid || request.auth.uid == resource.data.recipientId); } match /chatlist/{document} { allow write: if request.auth != null && (request.auth.uid == resource.data.uid || request.auth.uid == resource.data.recipientId); } } }
原拉取聊天列表代码
const fetchData = async () => { try { const messagesQuery = query( collection(db, "messages"), orderBy("createdAt", "desc") ); const messagesSnapshot = await getDocs(messagesQuery); const messagesData = messagesSnapshot.docs.map((doc) => doc.data()); console.log(messagesData,"messagesData"); const chatlistQuery = query(collection(db, "chatlist")); const chatlistSnapshot = await getDocs(chatlistQuery); const chatlistData = chatlistSnapshot.docs.map((doc) => doc.data()); console.log(chatlistData,"chatlistData"); const filteredData = chatlistData.filter( (item) => item.recipientId === currentUserUID || item.uid === currentUserUID ); const uniqueUsers = new Map(); filteredData.forEach((item) => { const existingItem = uniqueUsers.get( item.senderName || item.userName ); if (!existingItem || item.createdAt > existingItem.createdAt) { if (item.recipientId === currentUserUID) { item.senderName = item.userName; item.recipientId = item.uid; item.email = item.currentUserEmail; uniqueUsers.set(item.userName, item); } else { uniqueUsers.set(item.senderName, item); } } }); const updatedRecentChat = Array.from(uniqueUsers.values()).map( (item) => { const recentMessages = messagesData.filter( (message) => (message.uid === currentUserUID && message.recipientId === item.recipientId) || (message.uid === item.recipientId && message.recipientId === currentUserUID) ); const sortedMessages = recentMessages.sort( (a, b) => b.createdAt - a.createdAt ); if (sortedMessages.length > 0) { const recentMessage = sortedMessages[0]; return { ...item, recentMessage: recentMessage.text, recentMessageCreatedAt: recentMessage.createdAt, }; } else { // If there are no recent messages, set an empty string for the recentMessage field return { ...item, recentMessage: "", recentMessageCreatedAt: null, }; } } ); const sortedRecentChat = updatedRecentChat.sort((a, b) => { if (a.recentMessageCreatedAt && b.recentMessageCreatedAt) { return b.recentMessageCreatedAt - a.recentMessageCreatedAt; } else if (a.recentMessageCreatedAt) { return -1; } else if (b.recentMessageCreatedAt) { return 1; } else { return 0; } }); setRecentchat(sortedRecentChat); } catch (error) { return error; } };
原发送消息代码
const sendMessage = async () => { const { uid, displayName, photoURL } = auth.currentUser; const newMessage = { text: input, name: displayName, avatar: photoURL, createdAt: serverTimestamp(), uid: uid, userName: userName, senderName: senderName, recipientId: recipientId, }; try { await addDoc(collection(db, "messages"), newMessage); setInput(""); const newChatListEntry = { uid: uid, recipientId: recipientId, userName: userName, senderName: senderName, email: email, currentUserEmail: currentUserEmail, }; await addDoc(collection(db, "chatlist"), newChatListEntry); // Clear the message input field } catch (error) { console.error("Error adding document: ", error); // Handle the error as needed } };
问题排查与解决方案
1. Firestore规则核心问题
(1)写入规则依赖不存在的resource.data
使用addDoc创建新文档时,resource.data是未定义的(文档还未创建),原规则中request.auth.uid == resource.data.uid的条件永远不成立,直接导致写入权限被拒绝。
(2)规则重复定义导致冲突
原规则中对/messages/{document=**}和/messages/{document}重复定义,{document=**}是递归匹配所有子集合,普通{document}仅匹配集合下的文档,重复定义会引发规则逻辑冲突。
(3)全量读取未通过规则校验
拉取聊天列表时,代码先全量读取chatlist集合再前端过滤,但Firestore规则会先校验整个查询的合法性,全量读取请求无法证明所有文档都满足用户权限条件,直接被拦截。
2. 修正后的Firestore规则
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Messages集合规则 match /messages/{messageId} { // 读取:登录用户且是消息发送方或接收方 allow read: if request.auth != null && (request.auth.uid == resource.data.uid || request.auth.uid == resource.data.recipientId); // 写入:登录用户且是消息发送方(创建时用request.resource.data获取待写入数据) allow write: if request.auth != null && request.auth.uid == request.resource.data.uid; } // Chatlist集合规则 match /chatlist/{chatId} { // 读取:登录用户且是聊天参与方 allow read: if request.auth != null && (request.auth.uid == resource.data.uid || request.auth.uid == resource.data.recipientId); // 写入:登录用户且是聊天参与方(创建时验证当前用户是uid或recipientId) allow write: if request.auth != null && (request.auth.uid == request.resource.data.uid || request.auth.uid == request.resource.data.recipientId); } } }
3. 修正后的拉取聊天列表代码
const fetchData = async () => { try { const currentUserUID = auth.currentUser?.uid; if (!currentUserUID) return; // 直接查询当前用户参与的chatlist文档,避免全量读取 const chatlistQuery = query( collection(db, "chatlist"), or( where("uid", "==", currentUserUID), where("recipientId", "==", currentUserUID) ) ); const chatlistSnapshot = await getDocs(chatlistQuery); const chatlistData = chatlistSnapshot.docs.map((doc) => doc.data()); console.log(chatlistData,"chatlistData"); // 查询当前用户参与的消息,提前过滤权限范围内的数据 const messagesQuery = query( collection(db, "messages"), or( where("uid", "==", currentUserUID), where("recipientId", "==", currentUserUID) ), orderBy("createdAt", "desc") ); const messagesSnapshot = await getDocs(messagesQuery); const messagesData = messagesSnapshot.docs.map((doc) => doc.data()); console.log(messagesData,"messagesData"); const uniqueUsers = new Map(); chatlistData.forEach((item) => { const targetName = item.recipientId === currentUserUID ? item.userName : item.senderName; const existingItem = uniqueUsers.get(targetName); if (!existingItem || (item.createdAt && existingItem.createdAt && item.createdAt > existingItem.createdAt)) { const processedItem = { ...item }; if (item.recipientId === currentUserUID) { processedItem.senderName = item.userName; processedItem.recipientId = item.uid; processedItem.email = item.currentUserEmail; } uniqueUsers.set(targetName, processedItem); } }); const updatedRecentChat = Array.from(uniqueUsers.values()).map((item) => { const recentMessages = messagesData.filter( (message) => (message.uid === currentUserUID && message.recipientId === item.recipientId) || (message.uid === item.recipientId && message.recipientId === currentUserUID) ); const sortedMessages = recentMessages.sort((a, b) => b.createdAt - a.createdAt); if (sortedMessages.length > 0) { const recentMessage = sortedMessages[0]; return { ...item, recentMessage: recentMessage.text, recentMessageCreatedAt: recentMessage.createdAt, }; } else { return { ...item, recentMessage: "", recentMessageCreatedAt: null, }; } }); const sortedRecentChat = updatedRecentChat.sort((a, b) => { if (a.recentMessageCreatedAt && b.recentMessageCreatedAt) { return b.recentMessageCreatedAt - a.recentMessageCreatedAt; } else if (a.recentMessageCreatedAt) { return -1; } else if (b.recentMessageCreatedAt) { return 1; } else { return 0; } }); setRecentchat(sortedRecentChat); } catch (error) { console.error("拉取数据失败:", error); return error; } };
关键注意事项
- 复合索引创建:使用
or查询或where+orderBy组合时,Firestore会提示创建对应复合索引,直接点击控制台给出的链接创建即可。 request.resource与resource区别:创建新文档用request.resource.data获取待写入数据,更新/删除时用resource.data获取已存在文档的数据。- 禁止全量读取:所有读取请求必须在查询阶段过滤出当前用户有权访问的数据,否则规则会直接拦截请求。
内容的提问来源于stack exchange,提问作者am's
相关产品推荐
相关产品推荐

