SaaS 权限系统最容易被低估的地方,是很多开发者一开始只看到“用户有什么权限”,却没有继续追问“这个用户对哪个组织、哪个资源、哪一条数据以及哪一个字段有什么权限”。在一个真正运行起来的多组织 SaaS 中,权限并不是简单给用户贴上“管理员”“员工”“普通用户”三个标签,而是一套围绕身份、组织、成员关系、角色、资源和操作建立起来的授权体系。RBAC,也就是 Role-Based Access Control,只是其中一个重要组成部分,它解决的是角色与权限之间的关系,却不能单独解决所有的数据隔离和业务授权问题。
一个比较稳定的 SaaS 权限模型,首先应该把 User、Organization、Membership、Role 和 Business Data 分开。User 代表身份,Organization 代表租户或者工作空间,Membership 表示某个用户属于某个组织以及在这个组织中具有什么角色,Role 再关联具体权限。这样设计以后,同一个用户可以在 Organization A 中担任 Owner,在 Organization B 中只是普通 Member,而不能简单地把“管理员”写进 users 表后就认为这个人的全局身份已经确定。对于多租户 SaaS,这个区别非常重要,因为权限通常不是“这个人是谁”决定的,而是“这个人在当前组织中以什么身份访问什么资源”决定的。
因此,数据库结构通常至少需要 users、organizations 和 memberships 三个核心实体。memberships 表可以保存 user_id、organization_id、role_id、status、created_at 等字段,并通过唯一约束避免同一个用户被重复加入同一个组织。角色可以进一步拆分成 role 和 permissions,例如 owner、admin、manager、member、viewer 等角色分别关联 user.read、user.invite、project.write、billing.manage、report.export 等权限。这样新增角色时不需要在业务代码中到处增加 if 判断,而是通过权限配置组合出新的角色。
这种模型还有一个非常实际的好处,就是能够处理用户同时属于多个组织的情况。用户登录以后,系统需要知道当前请求针对哪个 organization,而服务器必须确认该用户确实属于这个组织,并且 membership 当前处于有效状态。客户端可以携带组织上下文,但 organization_id 绝不能被当成授权依据。假如用户把请求中的 organization_id 从自己所属的组织改成另一个组织的 ID,服务器仍然必须重新检查 Membership 和对应权限。如果后端只是根据浏览器提交的 organization_id 查询数据,那么所谓 RBAC 再完善,也可能被一个简单的参数修改绕过。
RBAC解决的是角色问题,不等于完整授权系统
RBAC 的核心思想是把权限授予角色,再把角色授予用户或者组织成员,从而避免直接为每一个用户维护一套完全独立的权限列表。这样可以把大量重复的授权关系集中管理。例如,项目管理员可以拥有 project.read、project.create、project.update 和 project.delete 等权限,审计员可能只有 project.read 和 audit.read,而普通成员只拥有项目读取和部分编辑能力。
但实际 SaaS 很快会遇到一个问题:拥有 project.update 权限,并不代表可以修改所有项目。一个部门经理可能有权修改本部门项目,却不能修改其他部门项目;一个客户管理员可能可以管理自己组织的所有用户,却不能访问平台内部运维数据。此时,单纯的 RBAC 就不够用了,因为系统需要进一步判断资源属于谁、用户与资源之间有什么关系以及当前操作是否满足业务规则。
这也是 RBAC 与 ABAC、Relationship-Based Access Control 等更细粒度授权模型产生交集的地方。工程上没有必要为了追求概念完整而一开始把所有模型全部引入,但应该让权限系统具备继续扩展的能力。比较实用的做法是先使用 RBAC 管理稳定的角色权限,再在具体资源层增加组织、项目、部门、资源所有者等条件。例如用户拥有 document.update 权限只是第一层判断,系统还要确认这份 document 是否属于当前 organization,以及当前用户是否拥有该文档或者是否属于允许编辑该文档的团队。
数据权限必须在服务器端完成
SaaS 权限系统中一个非常危险的误区,是把前端菜单隐藏当成权限控制。前端可以根据权限决定是否显示“删除用户”“导出数据”或者“系统设置”等按钮,但这些只是用户界面控制,并不是安全边界。攻击者完全可以跳过浏览器界面,直接调用 API。
因此真正的授权检查必须发生在服务器端。用户访问 /api/projects/123 时,后端不能只检查用户是否具有 project.read 权限,还必须确认项目 123 属于当前组织,并且当前用户对这个项目拥有相应访问资格。删除、修改、导出等操作也应该采用同样的原则。前端负责改善用户体验,后端负责执行安全策略,两者不能混为一谈。
对于多租户数据库,最常见的设计是在业务表中加入 organization_id 或 tenant_id。例如 projects、tasks、documents、invoices、customers 等数据表都保存所属组织。查询时不仅要根据资源 ID 查询,还必须把组织范围作为查询条件的一部分。这样即使攻击者知道另一个组织的资源 ID,也不能仅凭这个 ID 获得数据。
这个原则还应该延伸到缓存、对象存储、搜索索引和消息队列。很多 SaaS 数据泄露事故并不是主数据库查询忘记加入 tenant 条件,而是 Redis key、文件路径、搜索索引或者异步任务没有正确携带租户上下文。比如缓存不能简单使用 project:123 作为全局 key,而应该确保 key 的命名空间包含组织边界;对象存储中的文件访问也不能仅依赖一个猜得到的文件 ID。多租户隔离是一套贯穿整个数据链路的约束,而不是某一个 SQL 条件。
数据库RLS可以成为第二道防线
对于 PostgreSQL 等支持 Row-Level Security 的数据库,可以进一步使用 RLS 在数据库层面执行行级访问策略。这样即使应用代码某处出现遗漏,数据库仍然可以根据当前数据库会话中的租户上下文限制可以读取和修改的数据范围。
RLS 特别适合数据隔离要求较高的 SaaS,但它并不是所有项目都必须使用的技术。应用层授权和数据库 RLS 可以形成纵深防御,却也会增加连接池、事务上下文、调试和运维复杂度。如果系统已经通过严格的数据访问层统一处理 tenant scope,也可以先采用应用层控制,再根据安全要求逐步增加数据库级策略。关键不是为了使用 RLS 而使用 RLS,而是明确数据隔离到底在哪一层得到强制执行。
至于“列级权限”,也不应该简单理解成所有数据库都存在一个与 RLS 完全对应的通用 Column-Level Security 开关。很多系统实际上是在应用服务层根据用户权限决定哪些字段可以返回,或者通过数据库视图、存储过程以及专门的数据访问层实现字段隔离。例如销售人员可以读取客户基本信息,却不能读取银行卡号、内部成本或者其他敏感字段,这种场景往往需要字段级授权,而不仅仅是行级授权。
最小权限并不是把所有人都设置成只读
最小权限原则的核心不是“权限越少越安全”,而是让身份只拥有完成当前工作所必需的权限。一个财务管理员可能确实需要导出账务数据,那么 export.permission 对他而言就是必要权限;但这个权限并不应该自动意味着可以修改系统配置或者管理用户账号。
在企业 SaaS 中,职责分离同样重要。负责系统基础设施的运维人员不一定应该读取客户业务数据,负责客户业务的管理员也不应该获得修改底层系统配置的权限。尤其涉及支付、身份认证、API 密钥、数据导出和组织删除等高风险操作时,更应该考虑把权限拆分得足够清晰,并对高风险行为增加二次确认、审批或者额外认证。
权限检查也不应该停留在“有没有某个角色”这一层。例如代码中直接写 if ($user->isAdmin()),随着业务发展通常会迅速失控。更合理的设计是让业务代码询问一个明确的授权能力,例如当前主体是否拥有 invoice.export 权限,或者是否可以对指定 invoice 执行 export 操作。这样角色只是权限集合,业务代码不需要知道具体哪个角色拥有这个权限。
用户调岗是权限系统最容易暴露设计缺陷的地方
动态权限变化是 SaaS 实际运行中非常常见的场景。员工从销售部门调到财务部门,离职、转岗、临时参与项目、从管理员降级为普通成员,这些操作都会改变授权关系。如果权限直接硬编码在用户记录里,系统很快就会出现大量历史权限无法回收的问题。
比较合理的做法是让权限来源可追踪。用户加入组织后拥有一个 membership,membership 关联角色;如果组织结构还需要部门、团队或者项目级授权,则继续通过这些关系计算最终权限。当用户部门发生变化时,系统修改相应的 membership、team membership 或 resource assignment,权限计算随之发生变化,而不是在用户表里堆积越来越多的布尔字段。
权限撤销尤其需要考虑缓存。如果系统为了提高性能,把用户权限缓存到 Redis,那么用户被降权以后,旧缓存不能继续有效,否则数据库里的权限已经改变,应用却仍然按照旧权限放行。权限变更应该有明确的缓存失效机制,涉及高风险权限时甚至应该采用较短的缓存生命周期或者版本号机制。
审计日志不是普通的访问日志
一个成熟的 SaaS 权限系统还需要记录授权相关的安全事件。普通 HTTP access log 记录了请求来自哪个 IP、访问了哪个 URL,但这并不能完整回答“谁在什么时间以什么权限修改了什么资源”。
审计日志应该能够记录 actor、organization、action、resource、resource_id、timestamp、result,以及必要的请求上下文。例如某个管理员在某个时间邀请了一名新用户,某个用户修改了项目权限,某个管理员导出了客户数据,某个账号被提升为组织管理员,这些操作都应该能够被追踪。对于敏感操作,还应该考虑记录变更前后的关键值,但需要避免把密码、访问令牌、API secret 等敏感凭据直接写进日志。
审计日志本身也应该受到保护。允许普通管理员删除自己的操作记录,相当于让被审计对象控制审计证据。生产系统通常需要限制日志修改权限,并根据业务和合规要求设置保存周期。
权限系统的性能问题不能靠缓存掩盖
大型 SaaS 的权限检查确实可能产生大量数据库查询,但正确的优化顺序应该是先建立清晰的数据模型和查询路径,再针对热点进行缓存,而不是一开始把所有权限塞进 Redis。
组织成员关系、角色权限以及高频授权结果都可以缓存,但缓存 key 必须包含正确的组织和权限上下文。权限发生变化时,还必须能够及时失效相关缓存。如果一个用户同时属于几十个组织,或者一个角色拥有几百个权限,那么权限缓存的数据结构和失效策略也需要专门设计。
数据库索引同样重要。memberships 通常至少需要针对 user_id、organization_id 建立合理索引,并对 (user_id, organization_id) 建立唯一约束。业务表则应该根据访问模式建立以 organization_id 为重要组成部分的索引。否则随着租户和数据量增长,即使权限逻辑完全正确,查询性能也可能成为瓶颈。
不要把VPC当成SaaS权限控制
原文把 VPC 隔离与不同权限级别的数据访问联系起来,这个说法容易混淆网络安全与应用授权。VPC 主要解决网络边界、路由、子网、访问路径以及网络级安全控制问题,而“这个用户能不能读取这个客户的订单”属于应用和数据授权问题。
当然,两者可以共同组成纵深防御。数据库可以放在不直接暴露公网的私有网络中,应用服务器通过受控网络访问数据库;敏感服务可以进一步使用防火墙规则、私有服务连接、身份认证和密钥管理系统。但网络隔离不能代替 SaaS 的 tenant isolation。一个已经通过网络进入数据库的应用服务,仍然必须严格执行组织和资源级授权。
多组织SaaS应该从一开始设计租户边界
如果一个 SaaS 产品未来可能服务多个企业组织,那么 tenant boundary 最好从数据库和服务层建立,而不是等到客户数量增长以后再补。最常见的 shared database、shared tables 模式可以让多个组织共用数据库和表结构,每条业务数据通过 organization_id 区分所属租户。这种方式成本低、部署简单,也是大量 SaaS 产品常见的架构。
随着客户规模、合规要求和数据隔离需求提高,也可以采用 shared database、separate schema,或者 database-per-tenant 的模式。这里需要注意,database-per-tenant 并不等于每个客户必须拥有一台独立数据库服务器。多个数据库完全可以运行在同一个数据库集群上。对于少数高价值或者存在特殊合规要求的客户,还可以采用混合模式,把普通客户放在共享架构中,把特殊客户放入独立数据库。
无论选择哪一种模型,权限系统最重要的原则都没有改变:用户身份不能直接等同于组织身份,角色不能直接等同于数据访问权,前端显示控制不能等同于安全授权,数据库连接成功也不能等同于拥有业务数据访问资格。
一个成熟的 SaaS 权限体系最终应该形成多层授权结构。身份层负责确认“你是谁”,组织成员关系负责确认“你属于哪个组织”,角色和权限负责确认“你可以执行什么操作”,资源授权负责确认“你可以操作哪些对象”,数据隔离负责确认“你只能看到哪些组织和数据”,审计系统则负责记录“你什么时候执行过什么操作”。这几层如果能够从数据模型、API、中间件和数据库访问层统一起来,SaaS 的权限系统才不会随着用户数量和业务复杂度增长而逐渐失控。
权限设计的难点从来不是写出一个 isAdmin() 函数,而是建立一套不会因为业务扩张就不断打补丁的授权模型。对于多组织 SaaS,最值得长期坚持的设计原则可以归纳成一句话:User、Organization、Membership、Role 和 Data 必须保持清晰边界。只要这几个概念从数据库结构开始被严格区分,后面的 RBAC、资源授权、数据隔离、审计、缓存和合规要求才有可靠的基础。
SaaS 权限系统怎么设计?管理员、员工和普通用户应该如何划分
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
分享 Facebook | X | WhatsApp | LinkedIn
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP