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

Firestore安全规则实现列表仅返回当前用户参与的会话

问题根因
  • Firestore安全规则本身不具备自动过滤数据的能力,不会在查询时遍历集合剔除无权限文档,只会在查询发起前做静态校验:如果查询存在返回越权数据的可能,就会直接拒绝,不会执行查询。
  • 你写的测试代码2无任何过滤条件直接拉取全量conversations集合,必然会返回当前用户不在participants列表里的房间,被规则拦截是预期行为,正好符合你禁止全量拉取的需求。
  • 测试代码1被拦截的核心原因:
    • 现有规则没有校验查询中whereArrayContains传入的参数是否为当前登录用户的邮箱,规则无法确认你不会传入其他用户的邮箱越权拉取房间列表,因此判定查询不安全。
    • 你在查询中使用了type字段过滤、timestamp字段排序,但现有规则没有对这些查询条件做合法性校验,规则无法确认这些条件不会导致越权。
    • 原规则中authed()函数的判断顺序有问题:未先判断request.auth是否为空就直接访问request.auth.token属性,未登录场景下会直接抛出规则执行错误,也会导致权限校验失败。
修正方案

不需要在list规则中逐文档判断用户是否在房间参与人列表里,只要通过request.query对象强制校验所有list查询的过滤条件符合权限约束即可,这种方式性能更好,也不会误拦截合法查询:

  1. 强制所有conversations集合的list查询必须携带whereArrayContains("participants", 当前登录用户邮箱)过滤条件,从规则层面禁止用户传入其他用户的邮箱发起查询。
  2. 强制查询必须携带whereEqualTo("visible", true)过滤条件,匹配房间可见性要求。
  3. 单文档get操作保留逐文档权限校验,防止用户通过已知房间ID越权拉取单个房间信息。
  4. 修复authed函数的判空顺序问题,避免未登录场景下规则执行报错。

修正后的安全规则代码如下:

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

function authed() {
  return request.auth != null && request.auth.token != null && request.auth.token.email != null && request.auth.token.firebase.sign_in_provider == 'password';
}

function dataExists() {
  return resource != null && resource.data != null;
}

// 校验查询是否包含指定的where过滤条件
function hasWhere(filterField, filterOp, filterValue) {
  return request.query.where[filterField][filterOp] == filterValue;
}

// 校验list查询的所有强制过滤条件
function isValidConversationListQuery() {
  let userEmail = request.auth.token.email;
  return authed() 
    // 必须查询可见房间
    && hasWhere('visible', '==', true)
    // 必须查询当前用户参与的房间,禁止传入其他邮箱
    && hasWhere('participants', 'array-contains', userEmail)
    // 限制合法排序字段,避免通过异常排序绕过权限
    && (request.query.orderBy.size() == 0 || request.query.orderBy[0].field == 'timestamp' || request.query.orderBy[0].field == 'type');
}

function isConversationVisible() {
  return dataExists() && resource.data.visible == true;
}

function userInConversation() {
  let email = request.auth.token.email;
  return dataExists() && email in resource.data.participants;
}

function userInConversationUsingGet(convId) {
  let email = request.auth.token.email;
  let conv = get(/databases/$(database)/documents/conversations/$(convId));
  return conv.data != null && email in conv.data.participants && conv.data.visible == true;
}

match /conversations/{convId} {
  allow create, delete, update: if false;
  // list操作走查询条件校验,get操作走单文档权限校验
  allow list: if isValidConversationListQuery();
  allow get: if authed() && isConversationVisible() && userInConversation();

  match /messages/{messageId} {
    allow create, read, list: if userInConversationUsingGet(convId);
    allow delete, update: if false;
  }
}
}
}
客户端适配说明
  • 你的测试代码1中whereArrayContains传入的邮箱必须是当前登录用户的真实邮箱,即从FirebaseAuth.getInstance().getCurrentUser().getEmail()获取,不能硬编码字符串,否则会因为和规则中request.auth.token.email不匹配被拦截。
  • 带type过滤、timestamp排序的组合查询需要在Firebase控制台创建对应的复合索引,首次执行查询时报错信息里会带索引创建入口,创建完成即可正常查询,不属于权限问题。
  • 规则生效后,所有无过滤的全量查询、传入非本人邮箱的查询都会被直接拦截,完全满足防止越权拉取全平台房间数据的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 04:33:26