如何在Firebase Firestore中构建多租户SaaS应用权限体系
实现Firebase Firestore多租户自定义角色与权限机制
针对你的多租户场景,我之前做过类似的落地方案,核心思路是租户数据隔离+自定义角色权限映射+Firebase安全规则动态校验,下面分几个关键部分拆解:
一、数据结构设计(核心基础)
首先要把租户、用户、角色、业务数据做清晰的层级隔离,推荐这种高扩展性的结构:
tenants/ {tenantId}/ // 每个租户对应一个文档,比如tenantId设为customerA、customerB name: "客户A", roles: { // 租户自定义的角色-权限映射,由租户Admin自主维护 "Admins": { canManageRoles: true, canEditAllData: true, canInviteUsers: true }, "Managers": { canEditTeamData: true, canViewReports: true }, "Associates": { canViewOwnData: true }, "Contractors": { // 租户可随时新增自定义角色 canViewPublicData: true, canSubmitRequests: true } }, users/ // 该租户专属的用户子集合 {userId}/ // 关联Firebase Auth的用户ID displayName: "customerAUser1", role: "Admins", email: "user1@customerA.com" business_data/ {tenantId}/ // 业务数据严格按租户隔离,比如订单、客户信息等 orders/ {orderId}/ customers/ {customerId}/
这种设计的优势:
- 天然实现租户数据物理隔离,从根源避免跨租户访问风险
- 租户Admin无需依赖开发者,直接在
roles字段里新增/修改角色权限 - 用户与租户的关联关系明确,权限校验逻辑更直观
二、用户身份与角色绑定
用户登录后,需要将其与所属租户、角色关联,有两种常用方案:
1. Firebase Auth自定义Claims(适合角色不频繁变更的场景)
租户Admin通过Firebase Admin SDK给用户设置自定义Claims,携带tenantId和role信息:
// Admin SDK 代码示例(Node.js) const admin = require('firebase-admin'); async function assignUserRole(userId, tenantId, role) { await admin.auth().setCustomUserClaims(userId, { tenantId: tenantId, role: role }); }
安全规则里可以直接读取request.auth.token.tenantId和request.auth.token.role,校验速度快,性能开销低。
2. 结合Firestore用户文档(适合角色频繁变更的场景)
如果租户经常调整用户角色,自定义Claims的更新需要调用Admin SDK且有延迟,这时可以在安全规则中直接读取租户下的用户文档:
function getCurrentUserRole() { return get(/databases/$(database)/documents/tenants/$(request.auth.token.tenantId)/users/$(request.auth.uid)).data.role; }
注意:这种方式会增加Firestore的读取次数,需要在灵活性和性能之间做权衡。
三、Firebase安全规则实现
核心逻辑是:先验证用户属于当前租户,再根据其角色的权限配置校验操作权限。
下面是完整的规则示例:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // 通用函数:获取当前租户的角色权限配置 function getTenantRoleConfig() { return get(/databases/$(database)/documents/tenants/$(request.auth.token.tenantId)).data.roles; } // 通用函数:获取当前用户的权限集合 function getCurrentUserPermissions() { // 用自定义Claims的情况 let userRole = request.auth.token.role; // 如果用Firestore用户文档,替换成: // let userRole = get(/databases/$(database)/documents/tenants/$(request.auth.token.tenantId)/users/$(request.auth.uid)).data.role; return getTenantRoleConfig()[userRole]; } // 租户文档权限:仅该租户的Admin可修改角色配置 match /tenants/{tenantId} { allow read: if request.auth.token.tenantId == tenantId; allow update: if request.auth.token.tenantId == tenantId && getCurrentUserPermissions().canManageRoles == true; } // 租户用户管理:Admin可增删改用户,其他角色仅可查看 match /tenants/{tenantId}/users/{userId} { allow read: if request.auth.token.tenantId == tenantId; allow create, update, delete: if request.auth.token.tenantId == tenantId && getCurrentUserPermissions().canManageRoles == true; } // 业务数据权限:根据角色权限动态校验 match /business_data/{tenantId}/{collection}/{docId} { allow read: if request.auth.token.tenantId == tenantId && (getCurrentUserPermissions().canEditAllData == true || getCurrentUserPermissions().canViewReports == true || getCurrentUserPermissions().canViewOwnData == true || getCurrentUserPermissions().canViewPublicData == true); allow write: if request.auth.token.tenantId == tenantId && (getCurrentUserPermissions().canEditAllData == true || getCurrentUserPermissions().canEditTeamData == true || getCurrentUserPermissions().canSubmitRequests == true); } } }
四、租户Admin自定义角色的流程
- 租户Admin登录后,访问自己租户的
tenants/{tenantId}文档 - 在
roles字段中新增/修改角色,比如添加Auditors角色并配置权限:"Auditors": { "canViewAllReports": true, "canExportData": true } - 给租户下的目标用户分配该角色(通过后台界面调用
assignUserRole函数) - 安全规则会自动读取新的角色权限,无需修改规则代码
五、优化与注意事项
- 权限粒度细化:可以把权限拆到单个业务集合,比如
canEditOrders、canViewCustomers,让租户Admin能精准控制 - 性能优化:优先使用自定义Claims缓存租户和角色信息,减少安全规则中的Firestore读取;如果角色变更频繁,可以定期同步Claims或结合实时数据库做缓存
- 跨租户防护:所有业务数据必须按
tenantId隔离,安全规则中始终校验request.auth.token.tenantId == tenantId,彻底避免数据泄露
内容的提问来源于stack exchange,提问作者Raja Rao
相关产品推荐
相关产品推荐

