一个 SaaS 系统发展到一定规模以后,“一个用户到底是什么角色”往往会变成一个比想象中复杂的问题。
最简单的系统可以规定一个账号只能有一个角色,例如普通用户、编辑、管理员。但真正进入企业应用以后,同一个人可能同时承担多种工作:他可能是某个部门的负责人,同时又是项目管理员;在一个项目中拥有编辑权限,在另一个项目中却只能查看;甚至在同一个租户里,他是普通员工,在平台某个特定功能中又拥有管理权限。
如果数据库只给用户表增加一个 role 字段,这种需求很快就会把系统逼入死胡同。
更合理的做法,是把“用户是谁”和“用户能够做什么”分开。用户是身份主体,角色是权限集合,权限描述可以执行什么操作,而资源范围决定这些权限究竟可以作用于哪些数据。
一个账号为什么需要多个角色
假设一家企业使用 SaaS 系统,张三属于销售部门,同时负责一个项目。
在公司层面,他可能是“销售主管”;在项目层面,他又是“项目管理员”;对于财务模块,他可能只有查看权限。
如果系统只允许一个角色,就必须创建一个类似“销售主管+项目管理员+财务查看”的超级角色。
问题在于,这种角色会越来越多。
李四可能是销售主管加项目成员,王五可能是销售主管加财务管理员。几个月以后,系统里就会出现大量针对个人组合出来的角色。
这就是权限设计经常出现的“角色爆炸”。
因此,一个成熟的 SaaS 系统通常允许用户与多个角色建立关联。用户拥有多个角色后,系统再根据这些角色对应的权限计算当前请求是否允许执行。
用户、租户、角色和权限不能混在一起
多租户 SaaS 最容易犯的错误,就是把“用户是管理员”理解成一个全局属性。
实际上,一个用户可能属于多个租户,也可能在不同租户中拥有完全不同的角色。
例如同一个账号加入公司 A 后是普通员工,加入公司 B 后却是管理员。
因此,角色关系通常不能简单设计成:
user_id → role_id
而应该至少考虑租户或者组织上下文。
一个常见设计是使用用户、租户、角色和用户角色关联等数据结构。例如用户表保存账号身份,租户表保存企业或组织,角色表保存角色定义,用户角色关联表保存“哪个用户在什么租户中拥有哪个角色”。
这样,系统判断权限时首先确定当前请求属于哪个租户,然后再判断这个用户在这个租户中拥有哪些角色。
这一步非常重要,因为“管理员”不是一个脱离上下文的绝对身份。
RBAC 是基础,但不要把所有问题都塞进角色
对于绝大多数 SaaS 系统,RBAC,也就是基于角色的访问控制,是一个非常合适的基础模型。
角色可以定义成:
管理员可以管理用户;
编辑可以创建和修改文章;
审核员可以审核文章;
普通用户可以查看文章。
权限则可以进一步拆分成资源和操作。
例如:
article.read
article.create
article.update
article.delete
article.publish
这样,角色只是这些权限的集合。
“编辑”角色可以拥有 article.read、article.create 和 article.update;“审核员”可以拥有 article.read 和 article.publish。
这种设计比在程序代码中到处写:
if ($user->role == 'admin')
更加容易维护。
但 RBAC 解决的是“用户有没有某种能力”,并不自动解决“他可以操作哪一条数据”。
真正复杂的是数据范围
假设张三拥有 article.update 权限。
这是否意味着他可以修改整个 SaaS 平台所有租户的文章?
当然不是。
即使张三拥有编辑权限,也可能只能修改自己部门的文章,或者只能修改自己创建的文章。
因此,权限判断至少需要区分两个层面。
第一层是操作权限,也就是“能不能修改文章”。
第二层是资源范围,也就是“能修改哪些文章”。
例如一个角色可以拥有文章编辑权限,但数据范围限制为当前租户;另一个角色则只能操作自己创建的文章。
这也是为什么 SaaS 权限系统不能只依赖 RBAC。
当权限开始受到部门、地区、项目、资源所有者、组织层级等条件影响时,就需要加入更加细粒度的授权规则。这个时候可以采用 RBAC 与属性或策略条件结合的方式,而不一定需要把整个系统改造成一个复杂的 ABAC 策略引擎。
多个角色的权限通常应该怎么合并
假设一个用户同时拥有“编辑”和“审核员”两个角色。
编辑拥有:
article.read
article.create
article.update
审核员拥有:
article.read
article.publish
那么这个用户最终拥有的有效权限通常就是两个角色权限的集合。
也就是说,多个角色产生的是权限叠加,而不是角色之间互相覆盖。
因此,一般不需要人为给角色设置“管理员优先级100、编辑优先级50”这种机制来决定谁覆盖谁。
真正需要定义的是权限冲突规则。
例如一个角色允许访问某项资源,而另一个角色明确拒绝访问,系统到底采用允许还是拒绝?
这不能靠“哪个角色等级高”简单解决,而应该在整个授权模型建立统一规则。
一种常见做法是明确规定拒绝优先;另一种更简单的 RBAC 系统则尽量避免在角色层面设置显式 deny,只通过授予哪些权限来控制访问。
关键是规则必须稳定、可预测,而且所有业务模块都使用同一套授权逻辑。
角色继承也不要设计得太复杂
角色继承听起来很方便。
例如“管理员”继承“编辑”,于是管理员自动拥有编辑权限。
但角色层级一旦过深,权限关系就会变得很难理解。
管理员继承编辑,编辑继承普通用户,项目管理员又继承编辑,最后某个用户到底为什么拥有某项权限,开发人员甚至需要沿着多层继承关系才能查出来。
因此,对于大多数 SaaS 系统,角色继承应该保持简单。
如果只是为了减少重复权限,可以让高级角色包含基础角色的权限;但不要轻易建立几十层复杂的继承树。
更进一步的系统甚至可以不使用传统的角色继承,而是通过角色组合来解决权限复用问题。
权限模型最怕的并不是代码多,而是管理员自己都无法解释“为什么这个人拥有这个权限”。
不要把权限直接写死在前端
前端可以根据权限隐藏按钮,但这只是用户界面控制,不是安全控制。
例如页面上没有“删除用户”按钮,并不意味着用户没有删除用户的能力。
攻击者完全可以直接调用 API。
因此真正的权限检查必须发生在服务器端。
用户发送一个删除请求以后,后端需要重新确认身份、当前租户、目标资源以及操作权限。
例如用户拥有 user.delete,并不代表他可以删除另一个租户的用户。
后端必须同时验证目标用户是否属于当前租户,以及当前操作者是否有权处理这个资源。
这实际上也是防止 IDOR,也就是通过修改资源 ID 越权访问其他数据的重要措施。
权限缓存可以提高性能,但不能破坏权限一致性
大型 SaaS 系统每一次 API 请求都查询几十张权限表,当然会产生性能问题,因此权限结果经常需要缓存。
例如系统可以缓存某个用户在某个租户中的有效权限集合。
但权限缓存最大的风险就是“权限已经修改,而缓存还没有失效”。
管理员刚刚取消了某个用户的删除权限,如果旧权限还在缓存中,这个用户可能在一段时间内继续执行删除操作。
因此权限缓存必须设计明确的失效机制。
角色变化、用户角色关系变化、权限策略变化以后,应当及时使相关缓存失效。
对于高风险操作,还可以采用更加严格的实时检查,而不是完全依赖长时间缓存。
数据库设计应该保持清晰
一个基础的多角色 SaaS 权限系统,可以从这些核心数据开始设计:
users 保存用户身份。
tenants 保存企业或组织。
roles 保存角色。
permissions 保存系统权限。
user_roles 保存用户、租户与角色之间的关联。
role_permissions 保存角色与权限之间的关联。
业务数据表则根据具体业务增加 tenant_id、owner_id、department_id、project_id 等字段,用于确定资源属于谁。
这样,系统就能够分别回答几个不同的问题:
这个人是谁?
他属于哪个租户?
他在这个租户中是什么角色?
这个角色拥有哪个操作权限?
当前目标资源属于哪个租户?
这个用户是否有权对这条资源执行这个操作?
这几个问题分开以后,权限系统才容易继续扩展。
平台管理员和租户管理员必须分开
SaaS 系统还有一个经常被忽略的问题,就是平台管理员。
平台运营人员和客户企业管理员并不是同一种身份。
例如 SaaS 平台工作人员可能需要管理所有租户,而某家公司自己的管理员只能管理本公司的用户。
如果把两者都简单叫做 admin,后续很容易出现严重的权限边界问题。
因此最好明确区分平台级权限和租户级权限。
平台管理员拥有的是平台管理能力;租户管理员拥有的是当前租户范围内的管理能力。
即使某个租户管理员拥有“用户删除”权限,也不能因为 API 中存在 user_id 就允许他删除其他租户的用户。
权限系统一定要有审计记录
权限本身是安全敏感数据,因此角色和权限发生变化以后,最好留下完整的审计记录。
例如谁修改了谁的角色、什么时候修改、从什么角色改成什么角色、操作者属于哪个租户、修改了什么权限。
对于高风险操作,还应该记录具体资源和操作结果。
这样发生安全事件以后,系统才能回答一个非常现实的问题:
“这个人为什么在昨天晚上突然获得了管理员权限?”
如果没有审计日志,很多时候只能去翻服务器日志,甚至无法确定是谁做的。
不要为了“高级”而过度设计权限系统
SaaS 权限设计有一个很明显的误区,就是一开始就准备 RBAC、ABAC、策略引擎、角色继承、动态表达式、条件权限和复杂权限缓存。
结果系统还没有几个用户,权限代码已经比业务代码复杂。
更实际的路线通常是先建立清晰的 RBAC 基础,让用户可以在不同租户中拥有一个或多个角色,再增加资源范围控制。
当业务确实出现“同一个角色在不同部门权限不同”“只能操作自己创建的数据”“只能访问指定项目”等需求以后,再增加条件授权。
对于多数 SaaS 系统来说,一个结构清楚的 RBAC 加租户隔离,再结合资源级授权,就已经可以覆盖大量实际场景。
权限系统真正难的地方,不是把数据库表设计得多么复杂,而是让每一次授权判断都能够回答清楚三个问题:谁在什么租户中,以什么身份,对哪一个资源,执行什么操作。
一旦这几个概念在架构层面被明确下来,一个账号拥有多个角色就不再是特殊情况,而只是用户与角色之间多对多关系的一种正常表现。真正需要防止的,是角色、租户和数据范围彼此混杂,让一个看似普通的“管理员权限”最终变成跨租户访问数据的漏洞。
SaaS 系统如何处理一个账号对应多个角色,权限设计可以这样规划
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
分享 Facebook | X | WhatsApp | LinkedIn
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP