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

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)**时,需要提前验证整个查询结果的合法性,无法依赖逐条文档跨集合检查:

  1. 逐条调用get()查询list文档会导致严重性能问题;
  2. 规则无法提前判断所有符合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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 14:52:03