← 首页 / 访问控制 · 从 RBAC 查表到 policy 判定函数 待审核 18 页

访问控制 · 从 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 灰度、内容可见性闸门——它们都是同一个函数换了输入与输出。

decide( who, when/where, what, action ) permit | deny
who · 谁 when/where · 何时何地 what · 什么资源 action · 什么动作

RBAC:把「谁能对什么做什么」做成查表

核心 indirection (User→Role→Permission) 起步,拆 permission 的身份 module:resource:action、数据范围 Scope、元信息 Meta 与分配模版;再看最终生效集怎么算 union(roles) − deny + allow,最后落到 UI 层呈现、Server 层拦截与一段真实的历史包袱。

RBAC 的核心模型:

Permission 决定能不能做(article 能不能 update), Scope 决定对谁做(能 update 的是全部文章 / 本组 / 只有自己的)。两者正交 —— 同一个 action 配不同 scope,就是「编辑自己的」和「编辑所有人的」之差。
RBAC · why indirection

为什么:在 User 和 Permission 之间插一层 Role

直接给每个用户挂大量权限 (ACL),用户和权限是 M×N 根线,用户增多后难以维护、改一个策略要改几百处。RBAC 插一层 Role:User → Role、Role → Permission,线降到 M+N。调整用户/角色数量,对比两种模型连线数的增长速度。

Permission · 能不能做

Permission 的身份:module : resource : action [! state]

一条 permission 就是一个「能不能做」的最小单元。拆开它的 key:Module(context/domain 业务边界)/ Resource(操作对象)/ Action(read/create/update/deleteimport/exportsubmit/approve/reject/cancel/rollback/reopen/archive),外加可选的 !State 状态门。点选各段,实时拼出 permission key。

Scope · 对谁做

AllowedScope:同一个 action,对谁做

Permission 说「能 update」,Scope 说「能 update 谁的」。数据范围 Full ⊃ Group ⊃ Self ⊃ Public ⊃ None 是一条从宽到窄的链。切换 scope,看同一张数据表里哪些行变得可见——这就是 data-scope,Server 层与历史包袱两页都围绕它展开。

Meta · 清单 + 分配

能力清单与分配:Meta、Preset、单选 scope / 多选 action

每条 permission 带一份 Meta:Key / Title / Description / Risk(low·medium·high) / Category,让它在后台可读、可审计、可按风险高亮。分配界面里 Scope 单选(radio)Action 多选(checkbox),再用内置 Preset 模版快速填充常见组合。

resolve · union − deny + allow

最终生效集:User Override 怎么盖过 Role

用户的权限不止来自角色。公式:union(RolePermission) − UserDeny + UserAllow——先把多个角色的权限求并集,再减掉用户级 Deny,最后加上用户级 Allow(allow 后置,能盖过 deny)。勾角色、加 override,实时看每条权限来自哪个角色、被哪条 Deny 移除、被哪条 Allow 补回。

UI 层 · 菜单绑最低权限

UI 层:菜单/按钮绑权限,query 带 scope

前端按 Module/Resource/Action/Scope 决定给不给看、给不给点。菜单项绑定它要求的最低权限——没有就隐藏或置灰;列表请求把 scope 当 query 参数带上。切换用户权限,看菜单树实时显隐。强调:UI 只是体验,真正的闸门在 Server。

Server · 分层拦截

Server 层:从 Middleware 到 Repository 的逐层拦截

请求穿过 Middleware(认身份/角色)→ Router(声明所需 capability,any/all)→ Controller(只编排)→ Service(带上下文)→ Domain(业务规则)→ Repository(按 scope 过滤)。看 can / assert / scope 三个原语与 ScopeResolver 在哪一层介入,以及为什么对象级授权(object-level)必须下沉到数据层。

历史包袱 · 两套接口

历史包袱:/api/admin/article/list vs /api/article/list

许多老系统用两套接口区分数据范围:走 /admin/ 看全部,走普通接口只看自己的。本质是把 data-scope 硬编码进 URL——一种能运行但会逐渐失控的妥协。对照它与「统一接口 + scope 参数 + ScopeResolver」的差别,理解其维护成本所在。

ABAC / policy:把授权写成一个函数

decide(who, when/where, what, action) → permit/deny 的四个输入逐一拆开:who 的属性、when/where 的环境、what 的资源属性,最后 action 齐备交给 PDP,用组合算法裁决规则冲突。

request · 四元组

授权请求:把一次访问拆成 who / when·where / what / action

任何系统、任何检查,授权请求都是这四个输入拼出来的一个元组。动手装配一条请求,查看它的 JSON 形状;再看同一个元组在 XACML / AWS IAM / OPA 三套术语里只是换了名字 (Subject·Principal·input.user……)。建立全系列的词汇与心智模型:授权 = 函数,这四个是它的参数。

who · subject + 属性

who:身份只是开头,真正进规则的是属性

subject (principal) 不只是一个 id,它带着一组属性:role / dept / clearance / employed。RBAC 只读取 role;ABAC 让任意属性都能写进规则。切换用户、逐个拨动属性,看「账号停用」「密级不足」「admin 放行」「本人拥有」这些规则因 who 而亮 —— 同一个动作,换一个人结论便可能逆转。

when/where · 环境

when[where]:同一请求,换个时间地点就从准变拒

有一类条件既不属于人、也不属于资源——环境 (environment / context):当前时刻、星期几、来自哪个网络 (公司/家里/境外)、使用何种设备 (托管/个人)、是否已通过 MFA。它们使完全相同的 (who, what, action) 在工作日 10:00 从公司被准许、在深夜从境外被拒绝。拨动时钟、切换地点与设备,观察决策实时反转。

what · resource + 属性

what:资源也有属性 ——「自己的」和「密级」

资源 (resource) 同样带属性:owner / classification / status。两条最重要的规则都从这里来:owner === subject.id 就是「能改自己的」(基于关系,ReBAC 的种子);clearance ≥ classification 就是密级阶梯。改资源的归属与密级、切动作,看 r-owner / r-clearance / r-read 三条规则随 what 亮灭。

decide · rule + 组合算法

action + 全部合起来 → 决策:规则冲突时,谁说了算

四个输入齐备,交给 PDP。policy = 一组 rule,每条要么 permit 要么 deny。多条同时命中时如何处理?依靠组合算法:deny-overrides (有拒即拒,最常用)、permit-overrides (有准即准)、first-applicable (按序由第一条决定)。任意调整全部控件、切换算法,观察规则逐条点亮、盖下 PERMIT / DENY 印章,以及附带的 obligation。载入「admin 在境外导出」这类冲突场景,看三种算法给出不同答案。

实例深潜:policy 藏在你每天用的系统里

同一套 who/when/what/action → policy 并非纸上谈兵:HTTP 安全头、收银台促销、feature flag、内容可见性,逐一映回这个函数。

应用 · 现实里的同一个元组

现实世界:XACML / AWS IAM / OPA / Cedar 都是这个函数

这套 who/when/what/action → policy 并非纸上谈兵:XACML 的 Subject·Resource·Action·Environment 就是这四个轴;AWS IAM 是 Principal·Action·Resource·Condition + 显式 deny 永远赢;OPA/Regoinput.{user,action,resource} + default allow := false;Cedarpermit(principal, action, resource) when {…}。再对照 RBAC / ABAC / ReBAC / PBAC,以及 PEP·PDP·PAP·PIP 这套「函数住在哪」的架构。

实例 · HTTP / 浏览器

HTTP 中的 policy:浏览器是一台 PDP

Network 面板里那些 *-Policy 头并非杂乱无章的开关,而是同一件事:服务器声明策略、浏览器当场判定 permit/deny,且大多能解码回四元组 —— 只不过这里的 principal 是 origin。同源策略 = web 的 default-deny;CORS 放宽它 (who=Origin、action=method,带交互模拟器:简单请求 vs 预检、凭据下 * 失效);CSP 在它之上收紧 (who=子资源来源、what=directive,带评估器:default-src 回退、多份取交集 = deny-overrides、report-only = dry-run);再概览 Permissions-Policy / CORP·COOP·COEP,最后一张表把所有安全头映回 who/what/action。

实例 · 会员价 / 优惠券

会员价 + 优惠券:当 policy 不输出准/拒,而是算一个价

收银台算「应付 ¥XX」,本质就是把一套促销规则应用到购物车上 —— policy 的推广:决策不再是 permit/deny,而是一个值 (最终价) + 用了哪些优惠,即规则引擎。一张券 = 一条 rule (条件 满200/限图书/限新人 + 效果 −¥30/×0.95/包邮)。带一个收银台模拟器:调购物车数量、会员等级、勾券,切叠加策略 (互斥·自动选最省 / 先折扣后满减 / 先满减后折扣) 看实付实时变 + 逐条优惠的「省了多少 / 为何跳过」。重点演示「互斥 vs 叠加 = 组合算法的促销版」「应用顺序敏感(同车不同价)」「自动选最省 = 在 policy 空间做优化」,末尾一张表把满减门槛/互斥/顺序/封顶/默认价全映回 RBAC / ABAC 各页。

实例 · feature flag / 灰度

feature flag / 灰度发布:decide 出一个 variant

「先为 10% 用户开启新版」「内测账号直接开启、美区暂不开启」—— flag 求值同样是这个函数:who (用户+属性) → 哪个 variant。targeting 规则从上到下、第一条命中即生效,正是 first-applicable。含两个 demo:其一 flag 求值(总开关 kill switch → 个体名单 allowlist → targeting 规则 first-match → 百分比灰度 → 默认 variant 的完整轨迹,选 Ben/Cody 体会「顺序即优先级」);其二 灰度桶可视化——100 个用户按 hash(flagKey:userId)%100 分桶,拖动放量滑块即可看到哪些被点亮,讲透灰度的三个工程性质:稳定 (sticky)、单调 (放量只增不减、可回滚)、每 flag 独立。末尾把 targeting/first-match/allowlist/灰度分桶/默认/kill switch/variant 全映回 ABAC 各页。

实例 · 可见性 / 能不能看见

可见性:朋友圈三天、VIP 解锁、地区版权…… 都是同一个函数

「内容能不能让这个访客看见」也是这套四元组的判定:who (登录? 好友 / 关注 / 指定人? VIP 几级? 累计消费?)、when/where (地区 / 是否 App 内 / App 版本 / 距发布多久 / 今日已用次数)、what (审核状态 + 作者设定的可见范围)。把你能遇到的十二种可见性限制 (审查 · App 限制 · 登录 · 时间(三天/半年) · 好友/群/指定人 · 付费 VIP · 级别 sVIP · 关注 · 地区 · 次数 · 消费额 · 软件版本) 全部归位:它们只是读了四元组里不同的字段、各自拼成一道 deny 闸门。带一个可见性闸门模拟器:调访客与情境,看内容如何在「可见 / 受限」间翻转 —— 一串闸门做 AND (全过才可见 = deny-overrides),失败时按优先级只弹一个 CTA (登录 / 开通 VIP / 下载 App… = first-applicable + deny 携带的补救建议)。末尾一张表把十二种限制逐一映回它读的轴与 RBAC / ABAC 各页。

关于这些 demo:

所有 demo 纯前端、数据与规则写死在 perm.js(RBAC 半边)与 policy.js(ABAC 半边),目的只是把模型讲透;真实系统里 subject 来自 token、资源属性来自 DB、context 来自请求现场。这套模型常和 校验、表单、审计一起出现。