嵌入式应用授权
只向项目成员显示“编辑”按钮,不是服务端授权。更新接口若只检查用户已登录,合法令牌就能改写不属于主体的文档。成员关系散落在路由、中间件和 SQL 条件中,还会让同一个动作在不同入口得到不同答案;应用需要统一的 principal、action、resource、context 和可追踪拒绝结果。
另一套系统把权限判断抽成网络服务后解决了规则散落,却在服务抖动时把超时当成“先放行,稍后审计”。几分钟内,旧缓存继续允许已经被移出项目的账号下载敏感附件;恢复后,审计日志只记录远端调用失败,没有保存当时使用的策略版本、权限事实版本和最终资源执行结果。集中存放策略没有自动带来正确授权,本地快速返回也不等于低风险,真正缺失的是从可信身份到真实资源操作的闭环。
授权从四个对象开始
认证回答“来者是谁”,授权回答“这个主体现在能否对这个资源执行这个动作”。一个稳定的授权请求至少包含四部分:principal 是已经过可信化处理的主体,action 是业务动作,resource 是目标资源,context 是本次请求独有的环境信息。租户、资源所有者、项目成员关系通常属于实体或权限事实;来源 IP、MFA 状态、请求时间段和客户端风险等级更适合作为 context。两类数据混用,会让缓存失效、撤销和审计都变得含糊。
策略决策点只产生 Allow、Deny 或错误,真正阻止数据库更新、文件下载和消息发布的是应用里的策略执行点。执行点必须位于资源操作之前,并与业务事务、对象标识和租户边界绑定。前端隐藏按钮可以改善体验,但不能代替服务端执行;SDK 返回一个布尔值也还不是闭环,只有受保护操作确实遵守了决定,授权才算完成。
这条链上的每个对象都要能回答版本和所有权。主体声明由认证组件验证,业务服务负责把外部标识映射为稳定内部 ID;权限事实由拥有该关系的业务数据源提供;策略仓库保存可评审版本;应用实例装载并报告实际版本;执行点记录决定与操作结果。任何一个环节缺少版本,事故复盘就只能看到“当时应该是这条策略”。
三条路径解决的是不同问题
Cedar是策略语言、Schema 与 Rust 嵌入式授权引擎。它适合延迟预算紧、希望在进程内完成强类型策略判断、能够自己承担策略存储和分发的团队。它的授权组合语义固定:默认拒绝、任意 forbid 覆盖 permit、单条策略求值错误会被跳过并进入 diagnostics。Cedar 不保存业务关系,不提供网络 API,也不会替应用执行拒绝。
Casbin是一组多语言嵌入库,以 model、policy 和 Enforcer 组合 RBAC、ABAC、domain 等模型。它适合需要目标语言原生库、希望自定义 request、effect 和 matcher 的系统。灵活性的代价是模型语义由配置决定,不同语言实现、adapter、watcher 和 dispatcher 也有各自版本与能力。共享数据库只解决策略持久化,不证明每个进程已经装载同一份策略。
Oso Cloud 与 Polar把 policy 与 facts 放到集中服务,并提供布尔授权、资源列表、动作列表和本地数据库过滤等入口。它更适合需要集中管理权限事实、反向查询和多语言客户端的团队。应用承担网络超时、凭证、事实同步、执行点和业务数据库一致性;Hybrid 的只读 Fallback 能缓解部分失联,却必须接受权限撤销可能滞后。旧的 Oso 开源嵌入库已弃用,不应作为新项目默认起点。
| 项目事实 | 更自然的起点 | 必须接受的责任 |
|---|---|---|
| Rust 服务、单请求低延迟、策略语义需要静态验证 | Cedar | 自建策略发布、实体装配、审计和多实例一致性 |
| 多语言已有成熟 Casbin 实现,需要自定义 RBAC/ABAC 模型 | 目标语言 Casbin | 锁定实现与插件,验证模型效果和策略传播 |
| 需要资源列表、权限列表、统一 facts 与托管服务 | Oso Cloud | 网络依赖、API 凭证、双写一致性和失联策略 |
| 主要问题是海量关系元组、递归组和反向关系查询 | 关系型授权数据库 | 不要强迫进程内策略引擎承担图查询 |
不要只按语言数量选择。一个 Java 服务可以通过本地边车或网络服务使用其他引擎,但这会新增序列化、认证、超时和可用性问题;一个 Rust 服务也可能因为需要高效列表查询而更适合集中方案。先测量授权发生在单对象写入、批量列表、后台任务还是跨服务调用,再决定策略与权限数据放在哪里。
从最小部署闭环开始
进程内方案的“部署”不是启动一个独立控制台,而是把审核过的 SDK、策略快照、Schema 或 model 与应用版本一起装配:先创建只有一个资源、两个主体和一组正反请求的实验项目,再固定依赖版本,最后把执行点放在真实副作用之前。Cedar 与 Casbin 的安装命令、目录结构和第一组判断分别进入后续独立文章;任何本地引擎在进入多实例环境前,都要证明新旧策略可以并行装载、原子切换和立即回退。
集中方案先创建隔离环境和最小权限 API 凭证,再写入一组可删除的测试 facts,跑通单对象判断、列表查询、撤权和服务失联四条路径。只有应用能够区分明确拒绝、网络错误、数据陈旧和本地 Fallback,才允许把该客户端接入业务请求。部署完成的证据不是管理页面显示在线,而是正例执行成功、反例未触发资源操作、撤权在目标窗口内生效,并且每个结果都能追到策略与权限数据版本。
本地决策的低延迟来自责任回迁
进程内引擎省掉一次网络往返,也减少外部服务故障对请求路径的直接影响。然而策略、Schema、实体编码器和业务事实都必须由宿主装配。多实例发布时,不能逐文件覆盖正在使用的策略;应先在独立位置完成解析、验证和回归,再把不可变快照原子切换为当前版本。实例日志至少带上 policy_revision 与 data_revision,否则同一请求在不同实例得到不同答案时无法定位。
权限事实的切片决定正确性和性能。把整棵组织树和全部资源属性塞入每次请求会放大内存、解析与求值成本;只提供当前对象又可能漏掉组祖先、资源容器或策略读取的属性。团队应从策略访问模式推导最小实体闭包,为每类 action 建固定装配器,并用“完整闭包允许、删除关键祖先后拒绝或报错”的成对用例保护它。数据缺失不能静默解释成正常的无权限事实,至少要在错误分类和指标中可见。
缓存同样不是简单地保存布尔值。键中要包含所有影响决定的 principal、action、resource、context 字段,以及策略和权限数据版本。增加权限时,短暂旧拒绝通常影响可用性;撤销权限时,旧允许直接扩大暴露面,因此撤销不能只等待长 TTL。敏感写操作可以不缓存允许决定,或要求读到明确的数据版本。
集中能力需要把网络失败写进授权语义
远端服务可以统一策略和权限事实,提供列表或反向查询,也能减少每个应用重复实现发布能力。但一次授权请求由此增加 DNS、TLS、连接池、服务限流和租户凭证等失败节点。应用必须区分明确 Deny、调用超时、认证失败、数据版本落后和响应无法解析,不能把它们都折叠成 false,更不能把异常变成无限期 Allow。
失联策略按 action 风险分级。公开资料读取可以使用有版本上限的短期缓存;付款、密钥读取、成员管理和数据导出更适合 fail closed;紧急处置若需要 break-glass,应使用独立身份、短时审批、受限动作和完整审计,而不是全局关闭授权。恢复后还要核对服务决定与真实资源执行,不能以远端 API 恢复 200 作为事故结束。
列表查询尤其容易产生假闭环。先从授权服务取资源 ID,再查业务数据库时,必须再次绑定租户和资源类型,处理已删除对象、分页和同一事务视图。先分页业务数据再逐条过滤会造成空页、遗漏与 N+1 延迟;把授权过滤条件拼接到 SQL 时,则必须使用结构化查询和参数绑定,避免漏掉 predicate 或引入注入风险。
用同一组实验比较候选方案
选型实验不需要制造压测冠军,先固定一小组可解释数据:用户 Alice 是项目维护者,Bob 是访客;文档 A 属于该项目,文档 B 属于另一租户;read 与 edit 是两个 action;MFA 状态作为 context。对每个候选方案执行相同的正反路径:Alice 读取 A 应允许,Bob 编辑 A 应拒绝,任何人跨租户读取 B 应拒绝,撤销 Alice 的成员关系后旧允许必须在约定窗口内消失。
实验记录请求输入摘要、策略版本、权限数据版本、决定原因、错误类别、耗时和资源操作结果。进程内方案额外比较实例切换版本时是否出现混用;远端方案注入超时与陈旧副本;列表能力比较结果集合与逐对象授权是否等价。延迟数字只对锁定版本、固定数据规模和目标机器有效,文章或评审中不应把一次本机结果包装成通用容量结论。
排障沿着证据链反向走
看到“用户没有权限”时,先确认执行点是否真的拒绝了操作,再看决定是明确拒绝、默认拒绝还是求值错误。随后核对 resource 是否使用稳定内部 ID、action 是否规范化、principal 是否来自正确租户、context 是否包含预期认证姿态,最后比较策略和权限数据版本。反复修改策略而不检查请求装配,往往只会制造更多例外。
看到“撤权后仍可访问”时,优先找旧允许保存在哪里:应用布尔缓存、实体快照、Casbin 内存策略、远端 Fallback、消息队列积压或数据库副本。记录每个实例的 applied revision,再与撤销事件 revision 比较;只有全部执行点越过目标版本并通过真实资源反例,才能确认撤销完成。
看到“少量实例偶发拒绝”时,比较 diagnostics、实体闭包和版本,而不是先扩容。策略解析成功不等于 Schema 验证通过,策略验证通过也不等于请求与实体装配完整。对于 Cedar 要特别检查求值错误是否与 Allow 同时存在;对于 Casbin 要检查 model、adapter 和 watcher 的组合;对于 Oso 要检查 environment、facts 与本地过滤数据源是否一致。
团队交付的是可撤销的授权能力
策略进入代码评审,权限数据仍然需要独立的数据 owner。策略仓库应定义命名、Schema 兼容、测试语料、审批和回滚;业务团队维护实体编码契约与执行点;安全团队提供高风险 guardrail 与日志要求;平台团队维护发布、版本观测和凭证。角色分工不意味着串行交接,同一个变更必须把策略、数据生产者和应用消费方作为兼容事务验证。
审计日志保留关联 ID、脱敏后的 principal/action/resource、策略版本、数据版本或新鲜度、决定、决定原因、求值或网络错误、延迟和执行结果。完整 token、实体正文、组织关系、策略敏感常量和服务凭证不应进入普通日志。日志只记录“允许”而没有资源操作结果,会把执行点遗漏隐藏起来。
升级前用同一 corpus 回放新旧版本,比较 decision、reason 和 error,而不只比较允许率。迁移时旧路径继续执行,新路径只做 shadow decision;差异归零并不代表可立即切换,还要覆盖超时、撤销、列表与数据缺失。退出时导出策略、Schema 或 model、权限事实映射、实体编码契约、测试 corpus 和版本历史,停止所有消费者后再撤销凭证、清理缓存与删除旧环境。这样工具可以更换,授权语义和证据不会随实现一起消失。
