访问控制 · 从 RBAC 查表到 policy 判定函数
权限系统看似复杂,核心只回答两个正交的问题—— 能不能做(Permission)与 对谁做(Scope)。RBAC (Role-Based Access Control) 的做法是一层 indirection:User 绑 Role、Role 给 Permission,把权限当名词去查表。
把视角再抬高一层:每一次访问检查,本质上都是把当时的整体情况传入一个函数,由它当场判定 permit / deny——即下面这个 decide 函数。RBAC 是这个函数的特例:它的 who 只看一个属性 role。当 who 能带任意属性、再把 when/where(情境)与 what(资源属性)一并纳入,RBAC 就长成了 ABAC / 策略引擎 (XACML、AWS IAM、OPA、Cedar)。
最后落到现实:HTTP 安全头、会员价与优惠券、feature flag 灰度、内容可见性闸门、Kubernetes 的 Role 与文件的九个权限位——它们都是同一个函数换了输入与输出。
RBAC:把「谁能对什么做什么」做成查表
核心 indirection (User→Role→Permission) 起步,拆 permission 的身份 module:resource:action、数据范围 Scope、元信息 Meta 与分配模版;再看最终生效集怎么算 union(roles) − deny + allow,然后落到 UI 层呈现、Server 层拦截、数据层的列表与多租户、以及角色腐烂之后的治理,末尾是一段真实的历史包袱。
RBAC 的核心模型:
为什么:在 User 和 Permission 之间插一层 Role
直接给每个用户挂权限(ACL),用户与权限是 M×N 根线,改一个策略要改几百处。RBAC 插一层 Role,把连线降到 M+N。
Permission 的身份:module : resource : action [! state]
一条 permission 是「能不能做」的最小单元。它的 key 由 Module(业务边界)、Resource(操作对象)、Action(动词)三段构成,必要时再加 !State 状态门。
AllowedScope:同一个 action,对谁做
Permission 说「能 update」,Scope 说「能 update 谁的」。数据范围按权限由宽到窄分五档,从 Full 到 None,落实在查询的 WHERE 条件上。
能力清单(Meta)与分配:单选 scope、多选 action、Preset
每条 permission 带一份 Meta(Key / Title / Description / Risk / Category)让它可读可审计。分配界面遵循 scope 单选、action 多选,再以 Preset 模版填充常见组合。
最终生效集:User Override 怎么盖过 Role
用户权限不止来自角色。union(RolePermission) − UserDeny + UserAllow——先求角色并集,再减用户级 Deny,最后加用户级 Allow;Allow 后置,故能盖过 Deny。
UI 层:菜单/按钮绑权限,列表 query 带 scope
前端按 Module / Resource / Action / Scope 决定给不给看、给不给点。菜单项绑定它要求的最低权限,列表请求把 scope 当 query 参数带上。UI 只是体验,闸门在 Server。
Server 层:从 Middleware 到 Repository 的逐层拦截
请求穿过 Middleware 到 Repository 六层,每层管一件事。can / assert / scope 三个原语各自的介入点,以及对象级授权为何必须下沉到数据层。
数据层:一条记录之外,还有一整页列表
单条记录的 check 与整页列表的 filter 是两个问题。列表必须先过滤再分页,否则页数与总数都是错的;tenant 是最外层的 scope,交给应用层逐条查询手拼就总有漏的一天。生效集缓存则把撤销延迟重新引了回来。
治理:角色会腐烂,权限只增不减
模型正确的权限系统仍会在两三年里烂掉:例外堆成角色的笛卡儿积,继承边带来意外提权,授予有人申请而回收无人负责。这一页讲把它维持住的几件事,以及改动策略之前怎么先算清影响面。
历史包袱:两套接口区分数据范围
用两套接口区分数据范围,本质是把 data-scope 硬编码进 URL——能运行但会逐渐失控。对照它与「统一接口 + scope + ScopeResolver」的差别与迁移路径。
ABAC / policy:把授权写成一个函数
先补上 who 的来源——属性由 authn 交接过来,写进 token claims 还是判定现场回查,决定了它有多新鲜;再把 decide(who, when/where, what, action) → permit/deny 的四个输入逐一拆开:who 的属性、when/where 的环境、what 的资源属性,最后 action 齐备交给 PDP,用组合算法裁决规则冲突,并以 ReBAC 收尾——当关系本身成为授权依据,规则表就变成一张图。
授权请求:把一次访问拆成 who / when·where / what / action
任何系统、任何检查,授权请求都是四个输入拼出的一个元组。同一个元组在 XACML / AWS IAM / OPA / Cedar 里只是换了名字——授权是函数,这四个是它的参数。
identity:subject 的属性从哪来,能有多旧
授权函数的 who 是 authn 的产物。属性写进 token claims 还是判定现场回查,决定了权限改动后仍按旧属性放行多久;client 实际拿到的能力,是用户生效集与 token scope 的交集。
who:身份只是开头,真正进规则的是属性
subject 不只是一个 id,它带着一组属性 role / dept / clearance / employed。RBAC 只读取 role;ABAC 让任意属性都能写进规则——同一个动作,换一个人结论便可能逆转。
when[where]:同一请求,换个时间地点就从准变拒
有一类条件既不属于人也不属于资源——环境:时刻、星期、网络、设备、是否过 MFA。它们让完全相同的 (who, what, action) 在工作日从公司被准许、在深夜从境外被拒绝。
what:资源也有属性——「自己的」和「密级」
资源也带属性 owner / classification / status。两条关键规则由此而来:owner === subject.id 是「能改自己的」,clearance ≥ classification 是密级阶梯。
action + 全部合起来 → 决策:规则冲突时,谁说了算
四个输入齐备交给 PDP。policy 是一组 rule,多条同时命中时由组合算法收敛:deny-overrides 有拒即拒、permit-overrides 有准即准、first-applicable 按序由第一条决定。
ReBAC:权限是 user 与 object 之间的一条边
关系是一条边:doc#viewer@user,而非属性的一种取值。权限沿 parent 边向上传递,check 随之成为图上的可达性搜索。它省下的是每次授权与每次移动的写入量,不是权限的总条数。
实例深潜:policy 藏在你每天用的系统里
同一套 who/when/what/action → policy 并非纸上谈兵:HTTP 安全头、收银台促销、feature flag、内容可见性,以及基础设施层的 Kubernetes RBAC 与 POSIX 权限位,逐一映回这个函数。
现实世界:XACML / AWS IAM / OPA / Cedar 都是这个函数
主流授权系统都是 decide(who, when/where, what, action) 的实例,只是术语与语法不同。兼及 PEP·PDP·PAP·PIP 的分工与四种 BAC 模型的差别。
HTTP 中的 policy:浏览器是一台 PDP
那些 *-Policy 响应头都是同一件事——服务器声明策略、浏览器当场判定,且能解码回四元组,只不过 principal 是 origin。同源策略是 web 的 default-deny,CORS 放宽它,CSP 收紧它。
会员价 + 优惠券:当 policy 不输出准/拒,而是算一个价
收银台算「应付 ¥XX」是 policy 的推广:决策不再是 permit/deny,而是一个值加上用了哪些优惠——即规则引擎。互斥与叠加是组合算法的促销版,且顺序敏感。
feature flag / 灰度发布:decide 出一个 variant
flag 求值同样是这个函数:who 加属性决定看到哪个 variant。targeting 规则从上到下、第一条命中即生效,正是 first-applicable;未命中者进确定性 hash 分桶的百分比灰度。
可见性:内容「能不能看见」也是这个函数
十二种可见性限制只是读了四元组里不同的字段,各自拼成一道 deny 闸门。一串闸门做 AND 等于 deny-overrides,失败时按优先级只弹一个 CTA 等于 first-applicable。
基础设施层:Kubernetes RBAC 与 POSIX 权限位
Kubernetes 的 Role 与 verb 逐段对应 permission key,但它 additive-only、没有 deny。POSIX 权限位只落进 owner/group/other 中的一档,命中即终止。
关于这些 demo:
perm.ts(RBAC 半边)与 policy.ts(ABAC 半边),目的只是把模型讲透;真实系统里 subject 来自 token、资源属性来自 DB、context 来自请求现场。这套模型常和 校验、表单、审计一起出现。