--- tags: [rbac, abac, authorization, permission, access-control, opa] create time: 2026-05-17 21:35 --- # 从 RBAC 到 ABAC 的演进路径 ## 概述 RBAC(基于角色的访问控制)是绝大多数系统的起点,但随着业务复杂度上升,单纯的角色加权限映射会逐渐显露出局限。本文带你梳理从 RBAC 起步、最终演进到 ABAC(基于属性的访问控制)的完整路径,包括什么时候该升级以及如何平滑迁移。 > [!question] 什么时候 RBAC 不够用了? > > 假设你是某公司的财务主管,你有以下权限规则: > > - 你可以审批金额不超过 10 万的报销单 > - 你可以审批金额不超过 50 万的报销单,如果是本部门员工提交的 > - 你可以查看所有部门的财务报表,但不能修改 > - 如果你在周五下午 6 点之后尝试提交审批,会被阻止 > > 请问:这些规则能用纯 RBAC 表达吗? ## RBAC:入门最简模型 RBAC 的核心概念只有三个:用户、角色、权限。它们之间的关系如下: ```mermaid graph LR U1["用户: 张三"] --> R1["角色: 财务经理"] U2["用户: 李四"] --> R2["角色: 普通员工"] R1 --> P1["权限: 审批报销"] R1 --> P2["权限: 查看部门报表"] R2 --> P3["权限: 提交报销单"] R2 --> P4["权限: 查看个人记录"] ``` 最基础的 RBAC 判断实现方式: ```go func (svc *Service) HasPermission(user User, targetAction string, resourceID string) bool { roles := svc.roleStore.GetByUserID(user.ID) for _, role := range roles { perms := svc.permStore.GetByRoleID(role.ID) for _, perm := range perms { if perm.Action == targetAction && perm.Resource == resourceID { return true } } } return false } ``` > [!note] 解释 > > 这段代码是最直观的 RBAC 实现:遍历用户的所有角色,查找每个角色对应的权限表。问题是它只看 action 和 resource,不看任何上下文信息——金额大小、提交人身份、时间窗口这些全部被忽略。当业务规则开始依赖这些动态属性时,RBAC 就力不从心了。 RBAC 有其明确的适用范围,也有不可忽视的局限性: | 优势 | 代价 | |------|------| | 实现简单,一张权限表搞定一切 | 无法表达条件型权限比如按金额分级审批 | | 管理直观:分配角色等于分配权限 | 角色爆炸——为覆盖所有组合角色数量呈指数增长 | | 变更可控:改一个角色的权限所有成员同步更新 | 无法应对动态属性比如时间地域设备安全等级 | > [!tip] 何时 RBAC 够用? > > 如果你的系统满足以下条件,RBAC 就是最好的选择: > > 1. 权限规则固定变化频率低 > 2. 用户数量和组织结构稳定 > 3. 不需要按数据内容或上下文做差异化授权 > > 中小企业后台管理系统通常就是这类场景,用 RBAC 完全没问题。 ## 为什么需要升级?RBAC 的瓶颈 回到开头的财务审批例子,我们来拆解每一条规则为什么 RBAC 搞不定: | 规则 | RBAC 能不能表达 | 问题分析 | |------|----------------|---------| | 审批金额不超过 10 万 | 不行 | 金额不是角色或资源RBAC 的权限表中没有数值比较能力 | | 审批本部门员工提交 | 不行 | 提交人所属部门是数据属性不在角色权限体系中 | | 查看所有部门报表但不能修改 | 勉强 | 需要拆成查看和修改两个权限再加角色组合容易遗漏 | | 周五下午 6 点后禁止审批 | 不行 | 时间是运行时上下文RBAC 的权限定义是静态的 | 根本原因是:RBAC 的权限判定只依赖用户属于哪个角色这一个维度,而其他所有因素——数据内容、时间、环境——都被视为透明。当业务需要这些因素参与决策时,就需要引入更丰富的授权模型。 ## 过渡方案:规则引擎嵌入 RBAC 在全面升级到 ABAC 之前,可以先用规则引擎增强 RBAC 来处理一批常见的条件场景: ```go type ConditionalPermission struct { RoleID string `json:"role_id"` Action string `json:"action"` Condition string `json:"condition"` Expression func(ctx EvalContext) bool `json:"-"` } func (svc *Service) CheckConditionalPerm(user User, action string, ctx EvalContext) bool { perms := svc.condPermStore.GetByRoleAndAction(user.Roles, action) for _, perm := range perms { if perm.Expression(ctx) { return true } } return false } ``` > [!note] 解释 > > 这里的思路是在原有权限表的基础上增加一个 Condition 字段,用来存储条件表达式。CheckConditionalPerm 方法与原来的 HasPermission 类似,但它额外接收一个 EvalContext 包含运行时的上下文信息如请求金额、当前时间等。Expression 函数在上下文之上求值,判断当前条件是否满足。 > > 这种方式是在不重构权限模型的前提下快速解决问题。但当规则越来越多时,Condition 会变得难以维护——这就是需要 ABAC 的信号。 > [!question] 规则引擎和 ABAC 该怎么选? > > - 如果你只有 10 到 20 条条件规则,规则引擎完全够用 > - 当你发现有超过 50 条涉及不同数据字段的条件且经常新增时,ABAC 的结构化表达会显著降低维护成本 > > 判断信号很简单:当你开始在文档里写如果 A 并且 B 但是 C 除外这种句子时,就该考虑迁移 ABAC 了。 ## ABAC:结构化属性授权 ABAC 的核心思想是:权限判定不再只看角色,而是综合评估用户属性、资源属性、环境属性和动作本身。 ```mermaid flowchart TB UA["用户属性 department level tenantId"] --> EA["评估引擎"] RA["资源属性 amount ownerId sensitivity"] --> EA En["环境属性 time ip deviceRisk"] --> EA Policy["授权策略 IF user.level >= 2 AND resource.amount <= 100000 THEN ALLOW"] --> EA EA --> Decision{"Decision Allow Deny"} style UA fill:#e3f2fd style RA fill:#fff3e0 style En fill:#f3e5f5 style Policy fill:#e8f5e9 ``` 上面展示了 ABAC 评估的四个输入维度。与 RBAC 的最大区别在于,每个维度都可以参与最终的决策判断,而不是仅仅作为用户的一个标签。 ### 主流 ABAC 引擎:OPA Open Policy Agent 是目前最流行的 ABAC 实现,使用专用语言 Rego 编写策略: ```go import "github.com/open-policy-agent/opa/rego" func evaluate(opaPolicy string, input Input) (bool, error) { rego := rego.New( rego.Query("allow := data.authz.allow"), rego.Module("policy.rego", opaPolicy), rego.Input(input), ) result, err := rego.Eval(context.Background()) if err != nil { return false, err } return result.Allow(), nil } ``` 对应的 Rego 策略文件: ```rego package authz import input as request allow { request.user.roles[_] == "finance_manager" request.action == "approve" request.resource.amount <= 100000 } deny { request.action == "submit_approval" hour := time.hour(time.now()) weekday := time.weekday(time.now()) weekday == "Friday" hour >= 18 } ``` > [!note] 解释 > > Rego 是一种声明式策略语言,每一段 allow 或 deny 规则都是一个独立的布尔表达式。上面第一条 allow 规则表示:当前提条件同时满足——用户角色包含 finance_manager、操作是 approve、资源金额不超过 10 万时,授权结果为真。deny 规则优先级更高:即使是合法用户,如果在周五晚上 6 点后提交审批也会被拒绝。 > > 相比于 Go 代码中硬编码条件判断,Rego 策略可以独立部署和热更新,所有语言通过 HTTP 或 gRPC 调用同一个策略引擎,确保了授权决策的一致性。 ### RBAC 与 ABAC 的融合 不要把 RBAC 和 ABAC 看作替代品——RBAC 完全可以作为 ABAC 中的一个属性维度存在。大多数成熟系统是 RBAC 加 ABAC 的混合模式: ```rego package authz allow { some i request.user.roles[i] == "admin" } allow { request.user.roles[_] == "finance_manager" request.resource.amount <= 100000 now := time.now_ns() time.ns_to_rfc3339(now) > "09:00:00Z" time.ns_to_rfc3339(now) < "18:00:00Z" } deny { request.user.level < 3 request.resource.sensitivity == "top_secret" } ``` > [!summary] ABAC vs 嵌入式规则引擎对比 > > | 对比项 | 嵌入式规则引擎 | OPA ABAC | > |--------|-------------|-----------| > | 策略语言 | Go Python 代码 | Rego 专用声明式语言 | > | 独立部署 | 否随业务部署 | 是可独立运营 | > | 多语言通用 | 绑定业务语言 | HTTP gRPC 接口多语言共享 | > | 审计能力 | 需自行实现 | 内置策略决策日志 | > | 学习曲线 | 低 | 中高 | > > 混合模式下带来的收益包括:简单角色权限走 RBAC 分支速度快且省资源、条件型权限走 ABAC 分支灵活可扩展、Deny 优先级最高防止策略冲突导致的越权。 ## 迁移路线图 从 RBAC 平滑升级到 RBAC 加 ABAC 的建议路径: ```mermaid flowchart LR S1["L1 纯 RBAC"] -->|"遇到条件规则"| S2["L2 RBAC + 条件
代码内嵌"] S2 -->|"规则超过 50 条"| S3["L3 RBAC + OPA
策略独立"] S3 -->|"跨系统统一管理"| S4["L4 全量 ABAC
多租户动态"] style S1 fill:#c8e6c9 style S2 fill:#fff9c4 style S3 fill:#ffccbc style S4 fill:#f8bbd0 ``` 每个阶段的特征和常见误区: | 阶段 | 特征 | 建议做法 | 常见误区 | |------|------|---------|---------| | L1 到 L2 | 出现简单的条件判断 | 在现有权限表中加 condition 字段用脚本语言评估 | 把所有条件写成 if-else 大函数 | | L2 到 L3 | 条件变得复杂且需要多人协同维护 | 引入 OPA 把策略从代码中提取为独立文件 | 一开始就引入 OPA杀鸡用牛刀 | | L3 到 L4 | 需要跨多个系统统一策略 | 构建策略管理中心通过 HTTP 或 gRPC 下发决策 | 放弃 RBAC其实 RBAC 还有很大价值 | ## 实战 Checklist 落地权限系统时的经验教训汇总: | 项目 | 建议 | 踩过的坑 | |------|------|---------| | 缓存 | 权限结果缓存 5 到 15 分钟带版本号 | 每次请求查 DBP99 延迟飙到 200ms 以上 | | 默认行为 | 默认 Deny 明确授权才放行 | 默认 Allow 导致漏配策略时大面积越权 | | 测试 | 用 Policy-as-Code 的思想写单元测试 | 上线后发现一条遗漏的规则导致业务损失 | | 审计日志 | 记录每一次权限决策的输入和结果 | 出问题后不知道是哪个策略放的行 | | 紧急降级 | 设计熔断开关授权服务故障时 fallback 到本地缓存 | 授权服务挂了全站请求全部被拒 | ## 总结 RBAC 到 ABAC 的演进不是替换而是扩展。一个好的权限系统应该遵循以下路径: 1. **从简单开始**:先用 RBAC 覆盖百分之八十的常规场景 2. **渐进增强**:条件规则来了就用嵌入式规则引擎接住 3. **适时抽象**:规则多了就抽离为 OPA 策略获得独立管理和多语言复用能力 4. **永远保留 RBAC**:它仍然是最简单最高效的权限表达方式不应该被 ABAC 完全取代 > [!question] 读完之后想一想 > > 1. 你的系统中权限规则的变更频率是多少?每周几次以上就应该考虑引入独立策略引擎了? > 2. 如果授权服务完全不可用宕机加无缓存兜底你的系统会怎样?该如何设计降级策略? > 3. 在你的业务中哪些条件型权限将来可能会成为 ABAC 迁移的第一批候选? ## 关联笔记 - [[JWT与OAuth2对比]] — 权限模型与 JWT Claims 的结合方式 - [[09-网关鉴权策略]] — 网关层与业务层的权限分工