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:店铺所有者的用户UIDcreatedAt:店铺创建时间- 其他店铺装修、资质类基础配置字段
shopStaff子集合:挂在每个shops/{shopId}文档下,每个文档ID对应用户UID,只存店铺管理员、所有者的权限记录(普通消费者不需要在这里存),核心字段:role:枚举值,仅owner(所有者)/admin(管理员)两类grantedAt:权限授予时间grantedBy:执行授权操作的用户UID
products子集合:挂在shops/{shopId}下,存店铺在售商品信息,文档ID为商品唯一IDorders集合:全局存储所有订单,每个订单文档带shopId(所属店铺ID)、buyerUid(下单用户UID)字段,方便分别按店铺、按消费者维度查询
四、Custom Claims配置规则
Custom Claims只存两个轻量标记即可,完全不会触发大小限制:
- 用户首次注册完成时,默认打
isConsumer: true的claim,标记基础消费者身份 - 当用户被授予任意一家店铺的管理员/所有者权限时,给用户打
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 - 回收管理员权限时加个判断:如果该用户已经没有任何关联的店铺管理员/所有者身份,记得把他的
hasShopAccessclaim设为false,避免无效权限标记 - 前端路由守卫可以先读当前用户的claims,没有
hasShopAccess的用户直接隐藏商家后台入口就行,不用发额外请求
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

