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

Firestore集合组查询符合文档规则却失败,提示字段未定义

Firestore集合组查询安全规则问题排查

我在使用Firestore集合组查询时遇到安全规则问题,已参照官方文档案例配置规则,并使用规则模拟器测试。数据结构为forums集合下嵌套posts子集合,运行集合组查询获取author为id_authedDB的posts文档,预期仅返回1条符合条件的结果,但模拟器提示published或author字段未定义。以下是相关信息:

数据结构

forums/
   forum_id/
      posts/
          post_id2:{
            author:"another_autor" 
            published:true 
          }
   forum_id2/
      posts/
          post_id:{
            author:"id_authedDB" 
            published:true 
          }

安全规则

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }
    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

测试代码

const { readFileSync, createWriteStream } = require('fs');
const http = require("http");

const testing = require('@firebase/rules-unit-testing');
const { initializeTestEnvironment, assertFails, assertSucceeds } = testing;

const { doc, setDoc, setLogLevel, getDocs, query, collectionGroup, where } = require('firebase/firestore');

/** @type testing.RulesTestEnvironment */
let testEnv;

const projectId = "builder-d0c31"
const host = '127.0.0.1'
const port = 8080

before(async () => {
  // Silence expected rules rejections from Firestore SDK. Unexpected rejections
  // will still bubble up and will be thrown as an error (failing the tests).
  setLogLevel('error');

  testEnv = await initializeTestEnvironment({
  projectId,
  firestore: {
    host,
    port,
    rules: readFileSync('firestore.rules', 'utf8')
  },
  });

});

after(async () => {
  // Delete all the FirebaseApp instances created during testing.
  // Note: this does not affect or clear any data.
  await testEnv.cleanup();

  // Write the coverage report to a file
  const coverageFile = 'firestore-coverage.html';
  const fstream = createWriteStream(coverageFile);
  await new Promise((resolve, reject) => {
    const { host, port } = testEnv.emulators.firestore;
    const quotedHost = host.includes(':') ? `[${host}]` : host;
    http.get(`http://${quotedHost}:${port}/emulator/v1/projects/${testEnv.projectId}:ruleCoverage.html`, (res) => {
      res.pipe(fstream, { end: true });

      res.on("end", resolve);
      res.on("error", reject);
    });
  });

  console.log(`View firestore rule coverage information at ${coverageFile}\n`);
});

beforeEach(async () => {
  await testEnv.clearFirestore();
});


/////////////////////////
// Main test starts here
/////////////////////////

describe("posts", () => {

  it('should allow authedDB to get post', async function() {

    // create a user
    const uid = 'id_authedDB'
    const authedDB = testEnv.authenticatedContext(uid).firestore();

    // Setup: Create documents in DB for testing (bypassing Security Rules).
    await testEnv.withSecurityRulesDisabled(async (context) => {

      const db = context.firestore()
      await setDoc(doc(db, 'forums/forum_id/posts/post_id'), {author:uid, published:true})
      await setDoc(doc(db, 'forums/forum_id2/posts/post_id2'), {author:'another_autor', published:true})
    });

    await assertSucceeds(getDocs(query(collectionGroup(authedDB, 'posts', where("author", "==", uid)))));
  });

});

终端报错信息

终端报错信息


问题原因与修复方案

问题根源

Firestore集合组查询的安全规则会遍历所有匹配的posts集合文档,对每篇文档应用规则判断。当规则直接访问resource.data.published或resource.data.author时,若某篇文档缺失这些字段,就会抛出"字段未定义"的错误。

修复步骤

  1. 添加字段存在性检查:在authorOrPublished函数中先判断字段是否存在,再进行值比较,避免未定义错误。
  2. 优化写操作规则:强制创建/更新文档时必须包含指定字段,从源头避免字段缺失。

修改后的安全规则:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    function authorOrPublished() {
      // 先验证字段存在,再判断权限条件
      return resource.data.keys().hasAll(['published', 'author']) && 
             (resource.data.published == true || request.auth.uid == resource.data.author);
    }

    match /{path=**}/posts/{post} {
      allow list: if authorOrPublished();
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // 创建文档时强制字段存在且author为当前用户
      allow create: if request.auth != null && 
                     request.resource.data.author == request.auth.uid &&
                     request.resource.data.keys().hasAll(['published', 'author']);
      // 更新文档时验证用户为作者
      allow update: if request.auth != null && 
                     resource.data.author == request.auth.uid;
      // 删除操作仅作者可执行
      allow delete: if request.auth != null && 
                     resource.data.author == request.auth.uid;
    }
  }
}

额外验证建议

可以添加测试用例,模拟缺少published或author字段的文档,验证规则是否能正确拦截此类文档的访问,确保规则健壮性。

内容的提问来源于stack exchange,提问作者Tim Castelijn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 22:27:23