Firestore查询items集合权限不足问题的解决方案咨询
问题背景
我正在开发Firestore项目,数据结构如下:
集合结构
- lists 集合
{ "id": "12345", "title": "Example List", "owner": ["user1", "user2"] }
- items 集合
{ "id": "67890", "title": "Example Item", "listId": "12345", "toSchedule": true }
每个item通过listId关联对应list,lists的owner数组存储列表所有者ID。需求是配置Firestore安全规则,确保仅列表所有者可读写删除关联items。
当前Firestore安全规则
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /lists/{listId} { // 当owner数组包含用户UID时允许创建 allow create: if request.auth != null && request.resource.data.owner.hasAny([request.auth.uid]); // 用户为所有者时允许读写删 allow get, list, update, delete: if request.auth != null && request.auth.uid in resource.data.owner; } match /items/{itemId} { function isOwnerOfList(listId) { return get(/databases/$(database)/documents/lists/$(listId)).data.owner.hasAny([request.auth.uid]); } // 当owner数组包含用户UID时允许创建 allow create: if request.auth != null && request.resource.data.owner.hasAny([request.auth.uid]); // 用户为关联列表所有者时允许读写删 allow get, list, update, delete: if isOwnerOfList(resource.data.listId); } } }
前端查询代码
const Query = async (collectionName, additionalConditions = []) => { const conditions = [...additionalConditions]; return onSnapshot( query(collection(db, collectionName), ...conditions ), snapshot => Snapshot(snapshot, collectionName, dispatch), error => console.log('snapshot listener error', error) ) } Query('lists', [where('owner', 'array-contains', user)]) // 正常工作 Query('items', [where('listId', '==', '_uNygDJjiwhdEoUPnZO_')]) // 正常工作 Query('items', [where('toSchedule', '==', true)]) // 抛出权限不足错误
问题
执行Query('items', [where('toSchedule', '==', true)])时,报错:Uncaught (in promise) FirebaseError: Missing or insufficient permissions.
补充信息:
- 用户已认证,UID获取正常;
- 不想在items集合添加owner字段,因列表可共享给多用户;
- lists文档的owner数组包含当前用户UID;
- 怀疑问题与items集合的安全规则(尤其是isOwnerOfList函数)有关。
解决方案
问题根源
Firestore安全规则处理**列表查询(list)**时,需要提前验证整个查询结果的合法性,无法依赖逐条文档跨集合检查:
- 逐条调用
get()查询list文档会导致严重性能问题; - 规则无法提前判断所有符合
toSchedule == true的item对应的list是否属于当前用户,因此直接拒绝查询。
修改步骤
1. 修正Firestore安全规则
修复items集合的创建规则(原规则错误检查了不存在的item.owner字段),并优化查询规则的验证逻辑:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /lists/{listId} { allow create: if request.auth != null && request.resource.data.owner.hasAny([request.auth.uid]); allow get, list, update, delete: if request.auth != null && request.auth.uid in resource.data.owner; // 复用的列表所有者判断函数 function isListOwner() { return request.auth != null && request.auth.uid in resource.data.owner; } } match /items/{itemId} { // 获取当前item关联的列表文档 function getAssociatedList() { return get(/databases/$(database)/documents/lists/$(resource.data.listId)); } // 获取创建请求中item关联的列表文档 function getCreateAssociatedList() { return get(/databases/$(database)/documents/lists/$(request.resource.data.listId)); } // 创建item:验证关联列表存在且用户是所有者 allow create: if request.auth != null && exists(/databases/$(database)/documents/lists/$(request.resource.data.listId)) && getCreateAssociatedList().isListOwner(); // 单文档操作:验证用户是关联列表的所有者 allow get, update, delete: if request.auth != null && getAssociatedList().isListOwner(); // 列表查询:必须包含listId过滤条件,且该listId对应的列表用户是所有者 allow list: if request.auth != null && request.query.where('listId') != null && exists(/databases/$(database)/documents/lists/$(request.query.where('listId').value)) && get(/databases/$(database)/documents/lists/$(request.query.where('listId').value)).isListOwner(); } } }
2. 调整前端查询逻辑
先获取用户有权访问的所有listId,再将这些ID作为查询条件之一,结合toSchedule过滤:
// 获取用户有权访问的所有列表ID const getUserListIds = async (userId) => { const listsSnapshot = await getDocs(query(collection(db, 'lists'), where('owner', 'array-contains', userId))); return listsSnapshot.docs.map(doc => doc.id); } // 修改查询函数,自动添加用户listId过滤 const Query = async (collectionName, additionalConditions = []) => { let conditions = [...additionalConditions]; if (collectionName === 'items') { const userListIds = await getUserListIds(user.uid); if (userListIds.length > 0) { // 添加listId过滤,确保只查询用户有权访问的列表下的items conditions.push(where('listId', 'in', userListIds)); } else { // 无权限列表时返回空结果 return onSnapshot(query(collection(db, collectionName), where('listId', '==', '')), () => {}); } } return onSnapshot( query(collection(db, collectionName), ...conditions), snapshot => Snapshot(snapshot, collectionName, dispatch), error => console.log('snapshot listener error', error) ) } // 现在可正常执行该查询 Query('items', [where('toSchedule', '==', true)])
内容的提问来源于stack exchange,提问作者Maarten
相关产品推荐
相关产品推荐

