切换深色模式
第 5 章:给用户一个“管理员角色”,真的能解决授权吗?
本章主线:从角色爆炸和跨租户越权出发,逐步建立 RBAC、ABAC、ReBAC、策略架构、测试和灰度发布能力。
开场问题
小安为项目系统设计权限:普通用户、项目管理员、超级管理员。很快业务又提出地区管理员、只读管理员、临时管理员和外包管理员。角色越加越多,仍回答不了“为什么 Alice 能读这份文档”。
问题不只是角色数量,而是资源关系、动态上下文、例外生命周期和解释能力没有进入模型。授权设计先定义决策输入,再选择模型。
学习目标
能从业务对象定义 subject/resource/action;选择 RBAC、ABAC 或 ReBAC;设计 PAP/PDP/PEP/PIP;实现策略测试、影子发布、缓存撤销、解释和审计。
5.1 授权的六元组
text
(subject, resource, action, context, delegation, policy-version) → decision决策结果可以是 allow、deny、not-applicable,也可以附带 obligations:脱敏字段、限制行数、要求 MFA、记录理由、二人审批。
5.1.1 资源与动作命名
资源使用稳定、层次清晰的类型与 ID:tenant/t1/project/p9/document/d7。动作使用业务语义:document.read、invoice.approve,而非只用 HTTP GET/POST。相同 POST 可能是预览或付款,风险完全不同。
上下文只放决策当时可信且必要的信息。用户可控制的请求头不能直接成为 device_compliant=true。
5.2 ACL 与 RBAC
ACL 直接列出资源允许谁访问,直观但全局治理困难。RBAC 将权限赋给角色、用户获得角色,适合职责稳定的企业。
RBAC 失败模式:每个部门/地区/项目组合创建角色,形成角色爆炸;临时例外永久保留;角色名称不能解释真实权限;嵌套角色形成隐蔽继承。
治理方法:角色 owner、用途、成员规则、权限清单、有效期、定期复核、使用率与删除机制。
5.2.1 角色工程
先做 job/function role,避免按个人建角色。将通用低风险权限设为出生角色,高风险权限拆出申请型角色;临时项目权限必须有 expiry。统计角色成员分布、权限使用率和与同岗位偏差,识别角色污染。
5.3 ABAC
依据主体、资源、动作和环境属性判断。例如:department == resource.ownerDept AND device.compliant AND data.level <= subject.clearance。
ABAC 的难点是属性治理:来源是否权威、多久更新、缺失如何处理、语义是否一致、能否被用户修改。默认对缺失安全属性拒绝,避免把 null 当低风险。
5.3.1 示例策略
text
permit if
subject.tenant == resource.tenant
and subject.department == resource.owner_department
and subject.clearance >= resource.classification
and context.device_compliant == true
and action == "document.read"逐项测试 tenant 缺失、部门刚调动、设备信号过期、classification 未知和大小写/枚举不一致。策略正确性依赖属性契约。
5.4 ReBAC
用关系图表达 user:alice member team:payroll、team:payroll editor doc:q3。适合协作、分享和层次资源。
图 5-1:5.4 ReBAC · 可编辑 Mermaid 源码
设计需定义关系 schema、继承、环路、删除传播、图深度、缓存和一致性。回答“为什么允许”时返回关系路径,但要防止解释泄漏不可见对象。
5.4.1 Schema 思考
text
type document:
relation parent: folder
relation owner: user
relation editor: user | group#member
permission read = owner or editor or parent->readerSchema 变更属于数据迁移:旧关系是否仍有效、回滚如何解释、循环继承如何阻止、批量 list objects 如何高效执行,都需要测试。
5.5 策略架构:PAP/PDP/PEP/PIP
图 5-2:5.5 策略架构:PAP/PDP/PEP/PIP · 可编辑 Mermaid 源码
- PAP 管理策略生命周期;PDP 纯决策;PEP 必须在无法绕过的位置执行;PIP 提供可信属性。
- 把检查散落在 UI 中不算授权,攻击者可直接调用 API。
- 服务内部对象级授权仍需执行,网关只能覆盖粗粒度边界。
5.5.1 Check API 示例
json
{
"subject": {"type": "user", "id": "u123", "tenant": "t1"},
"action": "invoice.approve",
"resource": {"type": "invoice", "id": "i789", "tenant": "t1"},
"context": {"device_compliant": true, "auth_age_seconds": 120}
}响应:
json
{
"decision": "step_up",
"reason_codes": ["RECENT_PHISHING_RESISTANT_AUTH_REQUIRED"],
"policy_version": "payroll@8f37",
"obligations": [{"type": "max_auth_age", "seconds": 300}]
}PEP 必须理解并执行 obligations;无法识别的安全 obligation 默认拒绝,不能静默忽略。
5.6 策略即代码
需要版本控制、代码评审、静态检查、单元/属性测试、仿真、灰度、决策差异、回滚。策略发布与应用发布解耦,但不能无审批直达生产。
测试包括:明确 allow、明确 deny、属性缺失、跨租户、边界时间、继承、冲突策略、旧策略回放。安全不变量可写为“任何非本租户主体永不允许读取本租户资源”。
5.6.1 发布流水线
text
语法/类型检查 → 单元测试 → 安全不变量 → 历史流量回放
→ 新旧决策差异评审 → 影子模式 → 1% PEP 灰度 → 全量差异报告按 allow→deny、deny→allow 分类,后者通常风险更高。紧急 deny 可走快速通道,但仍记录审批、过期时间和事后评审。
5.7 冲突与默认策略
通常默认拒绝,显式允许;高风险 deny 可优先于 allow。需要写清组合算法,避免不同服务各自理解。Break-glass 是受控例外:强认证、短时、限定资源、理由、实时告警、事后复核。
5.8 缓存、一致性与性能
授权延迟直接影响业务。可缓存属性、关系或决策,但 key 必须包含租户、主体/资源版本和策略版本。撤权事件主动失效,TTL 限制最坏暴露窗口。
压测不能只测平均延迟:测试热点资源、深关系、批量列表、缓存冷启动、策略发布和 PIP 故障。列表接口要避免逐项 PDP 导致 N+1,可使用过滤条件/批量检查。
5.8.1 撤权暴露窗口
最坏暴露约等于:事件产生延迟 + 传播延迟 + 缓存 TTL + 客户端重试/会话延迟。只把缓存设为 5 分钟,不代表撤权 5 分钟内完成。对高危权限用主动失效、短 TTL 和资源端版本检查组合。
5.9 决策解释与审计
记录:主体、资源、动作、结果、理由码、命中规则、策略版本、关键属性版本、委托链、request ID。不要把完整敏感属性全写日志。
对用户给可理解但不泄密的解释;对调查员给受控的详细证据;对开发者给 trace。三者不是同一输出。
5.9.1 隐私与性能
审计不应复制整个 JWT 或属性对象。记录稳定 ID、属性版本/哈希和命中规则即可。高吞吐场景用异步可靠管道,但高风险写操作若审计完全不可用,要有明确降级决策。
5.10 选型方法
- 职位职责稳定:RBAC 打底。
- 风险和资源属性重要:叠加 ABAC。
- 协作分享和层次资源:ReBAC。
- 实际系统通常组合,但先确定主模型,避免“万能策略语言”掩盖混乱。
5.10.1 对比矩阵
| 维度 | RBAC | ABAC | ReBAC |
|---|---|---|---|
| 核心输入 | 角色成员与权限 | 主体/资源/环境属性 | 实体间关系路径 |
| 擅长 | 稳定岗位职责 | 动态上下文和数据分级 | 协作、分享、层次资源 |
| 主要风险 | 角色爆炸 | 属性不可信/策略复杂 | 图一致性和性能 |
| 解释 | 命中哪个角色 | 命中哪些条件 | 命中哪条关系路径 |
| 治理重点 | 角色 owner/复核 | 属性契约/来源 | schema/关系生命周期 |
5.11 本章实验与验收
实验:做文档系统 ReBAC;加入设备合规 ABAC;实现策略版本、批量 check、解释和影子发布。生成 10 万关系并测试 P99。
验收:能回答“谁能访问资源”“用户能访问什么”“为何允许”;证明跨租户不变量;演示撤权在约定 SLA 内生效;回滚错误策略。
5.11.1 评测数据集
构造至少 50 个固定案例:同租户正常、跨租户、禁用主体、过期关系、组嵌套、循环、属性缺失、策略冲突、break-glass、缓存旧版本。每个案例保存预期 decision 与 reason code,作为策略回归门禁。
工程复盘:角色没配错,为什么仍然发生跨租户读取
第一版授权规则写着“项目管理员可以读取项目文档”。小安认为角色判断没有问题,测试也覆盖了管理员与普通用户。评审同事只追问一句:项目是谁的项目?这才暴露出规则没有把租户和资源关系放进决策。
团队把授权测试从若干示例升级为安全不变量:不同租户的主体永远不能读取对方资源;被移出项目后不得继续命中缓存;策略服务超时时,高风险动作不能因旧 allow 结果继续执行。一个最小拒绝样例如下:
text
subject.tenant = t1
resource.tenant = t2
action = document.read
expected = deny(cross_tenant)小安随后增加策略差异检查、历史请求回放、影子决策和 deny→allow 高风险审批。发布后不只观察策略错误率,还观察决策延迟、缓存年龄、拒绝原因分布和权限扩大范围。
授权平台的价值不是让策略“能写”,而是让一次权限扩大能够被解释、测试、灰度、回滚并留下证据。
面试与设计评审
- RBAC、ABAC、ReBAC 怎么选?先说业务关系,再谈模型优缺点,避免“组合全部”。
- PEP 为什么必须不可绕过?用 UI 隐藏按钮但 API 可直调解释。
- 策略发布如何避免全站拒绝?回答测试、历史回放、影子差异、灰度、指标和快速回滚。
本章小结
授权是可测试、可解释、可演进的决策系统。角色只是输入之一;生产级能力还包括权威属性、关系路径、默认拒绝、策略版本、缓存撤销与决策审计。