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

Firebase单应用如何实现多类型用户体系与权限数据建模?

一、先明确你这套权限架构的专业名称

你要做的是资源级细粒度RBAC(基于角色的访问控制),是多租户电商场景的标准权限模型:核心是把「店铺」作为独立权限边界,所有角色权限都绑定到具体店铺资源,不给用户打全局单一角色标签——同一个用户在不同店铺下可以是消费者、管理员、所有者三种完全独立的身份,完全匹配你举的John的场景。

二、Firebase实现方案选型

别信网上说的全靠Custom Claims存所有权限的方案,Firebase Auth的Custom Claims是存在用户登录token里的,总大小硬限制1000字节,用户名下店铺稍多就会存爆,直接导致登录异常。
最优方案是Custom Claims + Firestore存储配合:Custom Claims只存高频校验的轻量权限标识,全量的店铺关联、权限分配数据存在Firestore里,这也是Firebase官方推荐的细粒度权限实现方式。

三、Cloud Firestore数据模型设计

根级集合按下面结构建,足够支撑你当前所有需求,后续扩展也方便:

  • users 集合:文档ID直接用Firebase Auth的用户UID,存用户昵称、头像、收货地址这类基础公开信息
  • shops 集合:每个店铺对应一个文档,文档ID为店铺自动生成的唯一ID,核心字段:
    • name:店铺名称
    • ownerUid:店铺所有者的用户UID
    • createdAt:店铺创建时间
    • 其他店铺装修、资质类基础配置字段
  • shopStaff 子集合:挂在每个shops/{shopId}文档下,每个文档ID对应用户UID,只存店铺管理员、所有者的权限记录(普通消费者不需要在这里存),核心字段:
    • role:枚举值,仅owner(所有者)/admin(管理员)两类
    • grantedAt:权限授予时间
    • grantedBy:执行授权操作的用户UID
  • products 子集合:挂在shops/{shopId}下,存店铺在售商品信息,文档ID为商品唯一ID
  • orders 集合:全局存储所有订单,每个订单文档带shopId(所属店铺ID)、buyerUid(下单用户UID)字段,方便分别按店铺、按消费者维度查询
四、Custom Claims配置规则

Custom Claims只存两个轻量标记即可,完全不会触发大小限制:

  1. 用户首次注册完成时,默认打isConsumer: true的claim,标记基础消费者身份
  2. 当用户被授予任意一家店铺的管理员/所有者权限时,给用户打hasShopAccess: true的claim,用来快速过滤完全没有商家后台权限的用户,减少无效查询和校验成本

注意:Custom Claims的所有修改操作必须在受信任的服务端执行,比如通过Cloud Functions写接口,绝对不能在前端直接修改claims,前端仅能读取当前登录用户的claims做路由守卫这类轻量判断。

给个可直接用的Cloud Functions授权逻辑示例(Node.js环境):

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

// 给用户授予店铺管理员权限的可调用接口
exports.grantShopAdmin = functions.https.onCall(async (data, context) => {
  // 第一步先校验调用者身份,不是店铺所有者直接抛错
  const shopRef = admin.firestore().doc(`shops/${data.shopId}`);
  const shopDoc = await shopRef.get();
  if (shopDoc.data().ownerUid !== context.auth.uid) {
    throw new functions.https.HttpsError('permission-denied', '仅店铺所有者可操作授权');
  }
  // 第二步写入店铺员工权限表
  await shopRef.collection('shopStaff').doc(data.targetUid).set({
    role: 'admin',
    grantedAt: admin.firestore.FieldValue.serverTimestamp(),
    grantedBy: context.auth.uid
  }, { merge: true });
  // 第三步更新目标用户的Custom Claims
  const targetUser = await admin.auth().getUser(data.targetUid);
  const existingClaims = targetUser.customClaims || {};
  await admin.auth().setCustomUserClaims(data.targetUid, {
    ...existingClaims,
    hasShopAccess: true
  });
  return { code: 0, msg: '授权成功' };
});
五、Cloud Firestore安全规则配置

下面的规则直接覆盖你提到的所有权限场景,复制过去改改就能用:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 工具方法:判断当前用户是否为指定店铺的所有者
    function isShopOwner(shopId) {
      return request.auth != null 
        && exists(/databases/$(database)/documents/shops/$(shopId)/shopStaff/$(request.auth.uid))
        && get(/databases/$(database)/documents/shops/$(shopId)/shopStaff/$(request.auth.uid)).data.role == 'owner';
    }
    // 工具方法:判断当前用户是否为指定店铺的管理员/所有者
    function isShopStaff(shopId) {
      return request.auth != null 
        && exists(/databases/$(database)/documents/shops/$(shopId)/shopStaff/$(request.auth.uid))
        && get(/databases/$(database)/documents/shops/$(shopId)/shopStaff/$(request.auth.uid)).data.role in ['owner', 'admin'];
    }

    // 店铺根集合规则
    match /shops/{shopId} {
      // 所有登录/未登录用户都能读取公开的店铺基础信息
      allow read: if true;
      // 仅店铺所有者可修改店铺基础配置
      allow write: if isShopOwner(shopId);

      // 店铺员工子集合规则
      match /shopStaff/{uid} {
        // 仅店铺所有者可读写员工权限配置,用来授予/回收管理员权限
        allow read, write: if isShopOwner(shopId);
      }

      // 商品子集合规则
      match /products/{productId} {
        // 所有人可读取公开的在售商品信息
        allow read: if true;
        // 仅店铺管理员/所有者可编辑、上下架商品
        allow write: if isShopStaff(shopId);
      }
    }

    // 订单集合规则
    match /orders/{orderId} {
      // 订单仅下单人、对应店铺的管理员/所有者可查看
      allow read: if resource.data.buyerUid == request.auth.uid || isShopStaff(resource.data.shopId);
      // 仅登录用户可给自己创建订单
      allow create: if request.auth != null && request.resource.data.buyerUid == request.auth.uid;
      // 仅店铺管理员/所有者可更新订单状态(发货、退款等)
      allow update: if isShopStaff(resource.data.shopId);
    }
  }
}
六、新手踩坑提醒
  • 别图省事把全量店铺权限都塞Custom Claims里,我见过不少新手踩这个坑,用户名下店铺超过10家就容易触发1000字节的token大小限制,直接导致登录失败
  • 要实现John查看名下所有店铺汇总的需求,直接前端查询shops集合中ownerUid == 当前用户UID的文档即可,查询效率很高,不需要走Custom Claims
  • 回收管理员权限时加个判断:如果该用户已经没有任何关联的店铺管理员/所有者身份,记得把他的hasShopAccess claim设为false,避免无效权限标记
  • 前端路由守卫可以先读当前用户的claims,没有hasShopAccess的用户直接隐藏商家后台入口就行,不用发额外请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 23:30:09