Skip to content

授权、角色与多租户隔离 ​

认证给出主体,授权决定该主体此刻能否对某项资源执行某个动作。用户能登录、不代表能读取任意订单;属于某个企业,也不代表能访问该企业全部数据。

OWASP Authorization建议默认拒绝,并对每次请求执行适当权限检查。前端菜单隐藏只影响展示,无法成为后端安全边界。

访问控制的基本概念 ​

访问控制(Access Control)根据规则允许或拒绝一次访问。一次判断需要明确四件事:

概念英文企业文档系统中的例子
主体Subject / Principal发起请求的员工小林
资源或对象Resource / Object文档 document:42
动作Action / Operation查看、编辑、分享、删除
策略Policy哪些员工在什么条件下能执行该动作

权限通常表达为“某类资源上的某项动作”,例如 document.read、document.edit。角色“编辑者”则是一组职责对应的权限集合。把权限判断写成 user.role == "admin",会把用户身份、职责和具体动作耦合在一起。

常见缩写中的 Access Control 都表示“访问控制”,前面的单词说明主要依据:

缩写英文全称中文含义
RBACRole-Based Access Control基于角色的访问控制
ABACAttribute-Based Access Control基于属性的访问控制
ReBACRelationship-Based Access Control基于关系的访问控制
ACLAccess Control List访问控制列表
PBACPolicy-Based Access Control基于策略的访问控制
DACDiscretionary Access Control自主访问控制
MACMandatory Access Control强制访问控制

其中 Role 是“角色”,Attribute 是“属性”,Relationship 是“关系”。ReBAC 中的 Re 用于区别 Role;ACL 中的 List 表示“列表”,更偏向记录资源授权的方式,可以与这些模型共同使用。

这些名称关注的维度不同,直接按“灵活性高低”排成一列容易误导选型:

维度对应机制要回答的问题
授权依据RBAC、ABAC、ReBAC根据角色、属性,还是关系作判断
授权记录ACL某项资源的访问权如何记录
授权裁量权DAC、MAC谁可以授予权限,哪些规则不能被所有者改变
策略表达与治理PBAC规则如何表达、发布、复用与审计

例如,文档所有者通过分享名单授权,可以同时具有 DAC 的裁量方式与 ACL 的记录形式;组织又可以通过属性策略禁止机密文档外发。多租户则是所有授权路径都要遵守的隔离约束,单凭“这是 SaaS”还不能决定采用 ABAC 或 ReBAC。

RBAC:通过角色组织权限 ​

RBAC 将“用户属于哪些角色”和“每个角色包含哪些权限”分开管理。一个用户可以有多个角色,一个角色也可以拥有多项权限;这一基本关系见 NIST RBAC 模型说明。

例如,企业文档系统定义“只读成员”和“编辑者”:

小林离职时撤销其成员资格或角色分配;“编辑者”新增某项权限时,集中修改角色权限即可,不必逐个修改所有编辑者的授权记录。

后端常把这条链路存成以下关系:

数据作用
roles定义角色及其适用范围
permissions定义稳定的资源动作标识
role_permissions角色与权限的多对多关联
role_assignments将用户分配到特定租户或项目范围内的角色

判断“小林能否编辑文档 42”时,先定位其在当前范围内的有效角色,再检查是否包含 document.edit;文档是否属于该租户、是否已归档,仍要单独判断。企业 A 的编辑者身份不能自动用于企业 B。

角色层级需要显式定义。例如“管理员包含编辑者能力”可以配置为权限继承,但不能仅根据角色名称推断。职责分离也需要约束:把付款发起与审批分成两个角色,并不会自动阻止同一用户同时拥有它们。

RBAC 适合组织职责相对稳定的后台和企业系统。限制在于角色数量:如果为每个部门、地区、项目、金额档位都建立独立角色,组合数量会快速增长。具体对象的临时分享和随请求变化的条件,通常交给关系或属性规则表达。

平台管理员与租户管理员分别建模。租户管理员只能管理当前租户允许的范围,不应通过一个全局 is_admin 布尔字段拥有平台级能力。

RBAC 可以限定到具体资源范围。Azure RBAC 的作用域可以落到单个资源;Casbin 的租户角色示例也将角色分配与租户关联。真正需要评估的是:每个文档是否都要独立维护大量角色分配;若权限主要由团队、目录与分享关系产生,关系建模通常更贴近业务变化。

ABAC:根据属性判断当前请求 ​

ABAC 将主体、资源、动作和环境的属性交给策略判断。NIST 对 ABAC 的定义包含这些属性及其与规则的匹配关系;属性可以随业务状态变化,不必把每种组合预先建成角色。

例如,公司要求某类内部文档只能由同部门员工在受管理设备上读取:

属性来源示例字段从哪里取得
主体所属企业、部门、在职状态账号和组织成员记录
资源所属企业、归属部门、发布状态文档数据库
动作document.read后端接口对应的业务操作
环境设备是否受管理、请求时间受信设备信息与服务端时钟

对应规则可以表达为:

text
允许读取 =
    主体仍在职
    且 主体企业 == 文档企业
    且 主体部门 == 文档部门
    且 文档状态 == 已发布
    且 动作 == document.read
    且 设备满足管理策略

小林调离部门后,只要授权判断读取到新的部门属性,就不再满足原规则。撤权能多快生效,取决于组织数据与授权缓存的新鲜度。

属性的可信来源决定策略是否有效。不能让浏览器自行提交 department=finance 或 managed_device=true,后端就把它当作事实;员工可编辑的个人资料也不能直接决定其审批权限。

ABAC 适合跨部门、资源分级、时间窗口和设备条件等动态限制。代价是规则解释与数据依赖:属性缺失、目录同步延迟或多条策略冲突时,都需要明确的处理方式。简单规则可以集中写在后端;引入独立策略服务后,还要管理规则版本、超时与缓存一致性。

ReBAC:通过关系推导资源权限 ​

ReBAC 关注“主体与谁、与哪些资源存在关系”,并按授权规则推导访问权。关系既可以在用户与对象之间,也可以在对象之间;例如父目录的查看者可以查看其中的文档。这种关系建模见 OpenFGA 的授权概念说明。

在企业文档系统中,可以记录:

主体或对象关系对象
小林是成员产品组
产品组的成员可查看设计目录
需求文档父目录是设计目录

如果策略明确规定“目录查看权可以向下继承”,小林就能通过这条关系链读取需求文档:

关系事实与授权规则分别维护。数据库里存在一条从用户到文档的路径,不代表这条路径上的任意关系都能产生访问权;“创建者”“评论者”“查看者”的含义,以及权限能否继承,都需要明确配置。

这也解释了资源角色与全局角色的差别:“编辑者”可以是用户对文档 42 的关系,并不意味着他能编辑全部文档。OpenFGA 的角色与权限示例展示了如何由对象上的角色关系获得具体权限。

ReBAC 适合团队协作、目录继承、项目成员和用户分享。资源移动、团队退组或分享撤回时,关系变化需要及时影响授权结果;多个父级、循环关系和大范围列表查询也会增加推导与缓存成本。不能只实现“检查一个对象”,却让搜索和导出绕过关系限制。

ACL:在资源上记录允许谁访问 ​

ACL 是资源的访问控制列表,记录哪些主体拥有哪些访问权,定义见 NIST ACL 术语说明。它常对应产品里的“分享设置”或“访问成员名单”。

例如,文档 42 的允许列表是:

资源主体允许的动作
文档 42小林查看、编辑
文档 42小周查看
文档 42产品组成员查看

这份表只保存允许项,未命中就拒绝。若产品同时支持显式拒绝、父目录继承和多个用户组,就要定义冲突规则;不能依赖“先查到哪条记录”决定结果。

ACL 的优点是直接:打开某个文档就能看到分享给了谁。代价在批量管理:逐个给几千份文档加人、撤人容易遗漏,需要用户组、继承规则或其他批量授权机制。

ACL 与 ReBAC 并非互斥。直接分享可以记录为列表条目,也可以表达为“用户是该文档的查看者”;加入团队成员与父目录关系后,还可以按策略推导间接访问权。选择表结构时需要同时考虑“谁能访问这个文档”和“这个用户能访问哪些文档”两类查询。

继承也不是 ACL 无法支持的能力。Windows 文件访问控制就包含访问控制条目的继承规则。自行设计时,需要明确继承是查询时推导,还是复制到子资源;后者会把撤销父级权限变成一次需要可靠传播的更新。

DAC 与 MAC:谁能决定授予权限 ​

自主访问控制(DAC)允许资源所有者或被授予管理权的主体决定访问者及其权限,见 NIST DAC 定义。文档作者邀请同事查看,是一种常见的业务形式;授权记录可以采用 ACL,也可以保存为用户与文档的关系。

“能编辑”与“能继续分享”应分别授权。小林获得查看权后,是否可以再邀请别人、是否能转移所有权、分享何时到期,都需要独立规则。否则一次有限授权可能通过转授扩散到最初未考虑的人群。

DAC 可以受到组织边界约束。例如文档所有者只能邀请本租户有效成员,不能分享给外部账号;企业禁用分享后,个人已有的分享权限也受限制。自主决定的范围需要被定义,不等于拥有者可以绕过全部平台规则。

强制访问控制(MAC)强调由统一权威制定并强制执行访问规则,普通资源所有者不能随意改变这些规则。NIST MAC 定义包括基于信息敏感标签与主体授权等级的访问控制,以及对转授和安全属性修改的约束。

在有分级要求的文档系统中,管理员可以维护资源分类、主体资格与允许的访问规则;作者即使拥有文档,也不能自行降低分类来绕过限制。具体模型还可能约束信息流向,不能只检查一次“等级够不够”。MAC 中的“强制”描述策略的约束力,不代表多个服务之间自动获得强一致性。

普通业务系统可以借鉴强制边界,但“机密文档不得外发”这一条属性规则,并不足以说明系统已实现完整的 MAC。需要继续审查分类变更、复制、下载、导出和管理接口,确认不存在绕过强制规则的路径。

PBAC:将授权规则作为策略管理 ​

基于策略的访问控制(PBAC)强调用可管理的策略表达授权规则。这里采用 Policy-Based Access Control 的含义;落地某个产品时仍要查看其术语定义与策略语义。

PBAC 与 ABAC 可以出现在同一套方案中:规则“在职员工只能从受管理设备读取本部门文档”按属性判断,属于 ABAC;将规则从业务代码中提取、版本化并交给统一策略引擎执行,则体现策略治理方式。策略也可以读取角色或关系判断结果,不必替换已有角色表。

Open Policy Agent(OPA)将策略决策与执行分离。业务代码准备可信输入并执行决策结果,策略引擎根据规则与数据计算结果。引擎不会自动知道员工是否离职,也不会自动在数据库查询、文件下载和后台任务中补齐检查。

借用 XACML 3.0 的访问控制架构术语,可以把这套职责拆成以下四个部分;这些是职责边界,不要求部署四个服务:

职责常见英文术语文档系统中的任务
决策Policy Decision Point,PDP根据输入和当前策略计算允许、拒绝及原因
执行Policy Enforcement Point,PEPAPI 或任务根据决策放行、过滤或终止操作
信息提供Policy Information Point,PIP提供成员状态、文档属性与受信设备信息
策略管理Policy Administration Point,PAP审核、发布、回滚策略及管理版本

统一管理策略不要求每次请求都远程访问一个中心。按 OPA 的管理架构,决策引擎可以靠近业务服务运行,再通过管理接口分发策略与收集日志。代价从远程调用转向了版本传播:撤权依赖的数据与策略何时到达各实例,需要单独定义。

当规则在多个服务中重复、需要独立审核发布,或者需要解释历史决策时,策略化更有价值。只有少量稳定规则时,一个有明确输入、默认拒绝且可验证的后端授权模块通常更容易维护;引入策略语言会增加调试、类型约束与发布治理工作。

模型怎么比较与组合 ​

对“小林能否查看文档 42”,几种机制关注的重点不同:

机制核心问题需要维护的数据主要代价
RBAC当前范围内的角色是否包含查看权角色权限、带作用域的角色分配职责组合过多导致角色膨胀,分配与撤销易遗漏
ABAC部门、状态、设备等条件是否满足可信属性、规则及其版本属性获取成本、数据滞后与规则冲突
ReBAC是否通过规定关系取得查看权团队成员、资源层级、分享关系关系推导、反向查询与撤权一致性
ACL资源名单是否覆盖小林或其用户组每个资源的授权条目批量授权、继承与权限盘点
DAC谁有权决定把文档分享给谁所有权、管理权与转授范围误分享、转授扩散与所有权移交
MAC所有者也不能改变哪些访问限制分类标签、主体资格与强制规则标签治理、信息流约束与例外管理
PBAC当前版本的策略如何作决定策略、输入契约、发布版本与决策记录策略测试、版本兼容与分发运维

它们不构成必须依次升级的路线。关系可以表达角色分配,角色也可以作为策略属性;具体实现往往同时包含多种机制。

对于仅在租户内分享文档的系统,可以把规则组合为:

text
允许读取 =
    当前账号与租户成员有效
    且 文档属于当前租户
    且(角色授予查看权限 或 有效分享关系授予查看权限)
    且 设备、文档状态等属性条件通过

这里的“或”允许岗位授权和单独分享成为两种授权来源;外层的租户、账号和属性条件则共同约束它们。显式分享不能绕过这个系统的租户隔离规则,也不能让已禁用成员继续访问。

实现时应保存必要的决策原因,例如命中了哪个角色、哪条分享关系或哪项拒绝条件。这样“为什么能访问”和“为什么被拒绝”才能被定位,而不是只得到一个无法解释的布尔值。

允许、拒绝与例外如何合并 ​

多条规则同时存在时,要先定义合并算法。对于这个文档系统,可以采用“硬边界全部满足、存在至少一种授权来源、未命中显式拒绝”才放行的规则。它是一项系统设计选择,不能假定所有引擎都按这个顺序处理。

请求情况此系统的结果原因
无编辑者角色,但有文档有效分享允许查看分享是被认可的独立授权来源
有编辑者角色,但租户成员已禁用拒绝成员状态是所有来源共同遵守的边界
有分享关系,但使用未受管理设备拒绝分享不能覆盖设备条件
有允许项,也命中该文档的显式拒绝拒绝此设计采用显式拒绝优先
设备信息缺失或策略计算失败不放行不能把无法判断解释为满足条件

策略服务故障时,接口可以返回临时不可用;这与成功算出“无权访问”在日志和错误语义上不同。若允许使用缓存决策,需要明确有效期及适用操作;敏感写入不能无限续用旧的允许结果。

紧急访问应有独立的授权、时限与审计规则。将平台管理员设为“跳过所有检查”会同时绕过租户隔离、禁用状态与强认证要求,使例外入口变成更大的权限边界。

按业务约束选择模型 ​

从权限变化方式开始 ​

选择依据应是权限为何发生变化、由谁维护,而不只是角色数量或用户规模:

主要需求可先采用的组合何时需要扩展
内部后台,岗位职责稳定带组织或项目作用域的 RBAC大量对象例外出现时,增加分享或属性条件
每份文档能邀请不同成员RBAC 管理后台能力,ACL 记录直接分享团队嵌套、目录继承成为常态时评估 ReBAC
协作空间、团队、项目、目录多级继承ReBAC 表达关系,角色管理组织职责设备、时间与资源分类等条件再由属性规则约束
多租户业务,成员在不同企业职责不同租户成员约束与租户内 RBAC动态条件成为主要规则时引入 ABAC;无需因多租户本身改用 ABAC
多服务重复同一套条件且需独立发布明确输入契约,再评估策略化管理策略分发、审核与回滚能力具备后再推广
资源所有者可以分享,但分级规则禁止部分流转有限 DAC 与组织强制规则组合若要求严格 MAC,进一步验证标签与信息流的完整约束

对于这里的企业文档系统,可以从租户内 RBAC、直接分享名单和少量集中属性判断起步。目录与团队继承成为主要需求后,再引入关系推导;多个服务确实需要共享规则时,再拆出策略管理与决策组件。迁移过程中保留同一个业务授权入口,避免调用方依赖具体表结构或引擎语法。

用查询与撤权成本校验选型 ​

“能判断一个对象”只是第一项能力。落地前还要用实际访问模式检验方案:

能力要验证的问题常见失败方式
单对象判断能否解释授权来源与拒绝原因只有布尔值,无法排查误授权
列表与搜索能否在分页、计数、聚合之前应用权限范围先分页再逐条过滤,漏项且泄露总量
批量操作能否对全部对象判断并定义部分成功语义只检查首项,后续对象跨租户
撤权成员、角色、分享和属性变化多久生效只删一张表,缓存与旧令牌继续放行
权限盘点能否反查某人能访问什么、某资源对谁开放只支持正向检查,无法离职清理
故障与恢复权威数据缺失、旧备份恢复时如何处理旧允许结果被无限复用或已删关系复活

OpenFGA 的查询接口区分 Check、ListObjects 与 ListUsers 等能力。选择引擎时应分别验证这些查询,不能从单次检查延迟推断大范围枚举也同样便宜。

列表过滤必须保留与单对象检查相同的语义。关系服务返回可访问对象集合后,业务查询仍要组合本地租户、状态和属性限制;若采用搜索索引中的权限投影,还要定义索引滞后期间的处理方式,敏感详情在返回前复核。

模型、引擎与部署方式分别选择 ​

方案适合先验证的需求仍由应用负责的部分
进程内授权模块规则少、事务与业务数据紧密关联统一入口、默认拒绝、调用覆盖与测试
Casbin 等访问控制库明确的角色、策略与作用域匹配模型配置、可信输入、数据同步及分布式失效
OPA 等通用策略引擎多类条件需独立表达、审核与发布属性采集、输入契约、策略分发与结果执行
OpenFGA 等关系授权系统对象关系、继承与资源授权查询关系写入、租户隔离、业务状态与一致性约束

这些方案不是同一模型的性能排名,也不要求互相替换。Casbin 支持多类模型,OPA 也可以表达角色与属性规则,关系判断还可以成为更外层策略的输入。独立服务额外增加网络调用、容量与可用性依赖;进程内执行则需要解决多实例的规则和数据同步。

迁移时先统一资源动作标识与授权上下文,再让新旧决策并行计算并记录差异。试运行阶段只由一套已确定的规则执行,不能采用“任意一套允许就放行”的合并方式。对跨租户、已禁用成员、分享撤销和目录移动等拒绝场景验证后,再逐步切换调用方。

主体、租户、动作与对象 ​

建议业务授权入口显式接收以下上下文:

text
authorize(
    principal,
    tenant_id,
    action = "invoice.refund",
    resource,
    business_context
)

这是职责示意,不是某个框架的可运行 API。principal 来源于已验证会话;tenant_id 可以来自路由或客户端选择,但必须验证主体对该租户的有效成员资格。

授权判断至少组合:账号可用、成员有效、角色允许该动作、资源属于该租户、当前业务状态可执行。客户端传来的角色、用户 ID 或资源所有者字段不能覆盖服务端事实。

以租户内的一次敏感写入为例,先完成访问检查,再进入业务事务。认证检查包含账号可用性,租户与对象授权包含成员资格、资源归属和动作权限;箭头表示判断依赖,不要求分别查询数据库。

事务内再次检查关键权限、业务状态和幂等条件,防止前面的判断在并发变更后失效。

强认证只补足身份保证,不授予新角色;完成后要重新判断当前权限与业务状态。错误码和隐藏资源存在性的策略见后文。

对象级检查放进数据访问边界 ​

下面是 PostgreSQL 查询示例,假设 :user_id 已由服务端认证取得,参数通过数据库驱动绑定:

sql
SELECT i.id, i.status, i.total_amount
FROM invoices AS i
JOIN memberships AS m
  ON m.tenant_id = i.tenant_id
 AND m.user_id = :user_id
 AND m.status = 'active'
WHERE i.id = :invoice_id
  AND i.tenant_id = :tenant_id;

查询限制了租户与有效成员,还没有证明该成员具有发票查看或退款权限。动作权限应由统一授权逻辑继续检查;写操作还需要并发与业务状态约束。

随机 UUID 减少可猜测性,但不能替代对象授权。详情、列表、搜索、导出、批量修改、附件下载和 WebSocket 订阅都要使用相同范围,不能只保护详情页。

多租户容易遗漏的边界 ​

位置常见遗漏设计约束
缓存只按资源 ID 或用户 ID 缓存结果key 包含必要租户及授权范围
关系引用租户 A 的记录关联到租户 B 的子对象创建与修改时检查引用归属
批量操作只验证第一项或不报告部分失败明确全有全无或逐项授权语义
后台任务入队时有权,执行时已离职执行时重新校验或使用明确的任务授权策略
对象存储长期公开 URL 绕开业务鉴权控制下载入口与签名 URL 时限
管理工具内部工具可任意指定用户和租户独立高权限入口、最小权限与审计

数据库行级安全(Row-Level Security,RLS)可作为额外防线,但连接池复用时必须正确设置和清理租户上下文。是否启用 RLS、如何与运维账号配合,需要单独设计,不能只依赖应用给连接设置一次变量。

scope 与业务权限 ​

OAuth scope 描述令牌被授予的访问范围。即使包含 orders:read,仍要检查用户能读哪些订单;scope 不会自动生成租户成员关系。

同样,JWT 中的角色只是签发时声明。移除管理员、退出企业或资源转移后,旧声明可能仍在有效期内。敏感动作应读取足够新的成员与资源状态,或通过版本和受限缓存实现明确的权限传播时限。

权限撤销与事务竞态 ​

“请求进入时是管理员,提交前已被撤权”属于检查与使用之间的竞态。对普通读请求可以接受已定义的短时窗口;重要写入则应把关键条件纳入事务。

可以锁定相关成员或授权版本,在事务中再次检查,并确保撤权流程采用相容的锁定和版本协议;仅查询一次不能自动阻止并发变更。远程策略服务无法与本地数据库自然组成单库事务,需要额外定义决策有效期和版本约束。

资源状态同样重要。退款前检查订单可退款,写入时仍要用状态条件或版本控制防止重复退款。权限正确不能替代业务幂等。

错误语义与验证 ​

RFC 9110区分认证挑战与拒绝访问。API 通常对缺少有效认证返回 401,并按所用认证方案提供适用的挑战;已认证但无权通常返回 403。为了隐藏资源存在性,可以在明确策略下使用 404,并保持列表、详情和日志行为一致。

授权验证应以“主体 × 租户 × 资源 × 动作”为矩阵,至少覆盖匿名、普通成员、管理员、已禁用成员、跨租户资源和批量请求。策略新增动作时默认拒绝,可以让遗漏尽早暴露。

参考资料 ​

工程实践与社区文章 ​