权限模型设计:RBAC 与 ABAC

权限这块,很多团队一开始都会选 RBAC,因为简单直观。但一旦业务复杂起来,角色数量爆炸,你就会发现 RBAC 不够用了。我这些年做过的安全集成项目里,至少有一半最后都不得不引入 ABAC 做补充。

说白了,RBAC 是“你是谁”决定你能干什么,ABAC 是“你 + 环境 + 资源”一起决定。咱们今天就把这两个模型拆开看透,再聊聊怎么把策略引擎嵌进去。

RBAC:角色就是通行证

RBAC 的核心思路很朴素:

  • 用户 → 角色 → 权限
  • 一个角色对应一组权限集合
  • 用户加入角色,就继承了权限

我记得早年做企业内部系统时,最常见的角色就是“管理员”、“编辑”、“访客”。每个角色背后是一个权限矩阵,直接在代码里写死。后来开始用策略引擎,才把权限抽离出来。

RBAC 的优点

  • 模型简单,开发理解成本低
  • 权限分配快,改角色即可批量改权限
  • 审计直观,谁有什么角色一目了然

但坑也明显。我遇到过一家公司,业务线 20 多条,角色数量飙升到 300 多个。一个运营需要“内容编辑 + 数据查看 + 活动管理”,结果他们创建了“运营-内容-数据-活动”复合角色,名字长得像八宝粥。这种时候 RBAC 就变成了角色爆炸。

避坑:角色爆炸

我曾经在一个 SaaS 项目里接手过 400 多个角色。查下来,三分之一是重复的。团队为了省事,每来一个新需求就新建角色,从不复用。最后我用 RBAC 分层 + 角色继承才压到 80 个。记住:角色数量超过 50 个就要警惕,超过 100 个必须分层。

ABAC:用属性说话

ABAC 不做角色,而是用属性来评估。属性来自三方面:

  • 用户属性:部门、职级、区域、安全等级
  • 资源属性:文档分类、敏感度、创建时间
  • 环境属性:时间、IP、设备类型、网络位置

策略引擎收到请求后,把这三组属性拿过来算一下,符合策略就放行,不符合就拒绝。

举个例子:一个文档系统里,策略可以写成“只有研发部的人,在工作时间(9:00-18:00),从公司内网访问,且文档敏感度≤2,才能查看”。这在 RBAC 里得拆成好几个角色和条件,而在 ABAC 里就是一条策略。

RBAC vs ABAC:对比一下

维度 RBAC ABAC
授权粒度 粗,基于角色 细,基于属性组合
策略动态性 差,修改角色需审批 强,属性变化即时生效
管理复杂度 低(角色少时) 高(需维护属性定义和策略)
适用场景 组织架构稳定、权限类型少 多维度条件、动态环境、合规要求高
性能开销 低,角色到权限的映射简单 中等,需解析属性并匹配策略
审计难度 容易,看角色即可 需要记录属性上下文,否则看不清

我的建议

别上来就全上 ABAC。先评估你的业务场景:如果权限规则 80% 可以靠角色搞定,那就用 RBAC 打底,再在需要细粒度的地方引入 ABAC 做补充。我做过的一个金融项目就是 RBAC+ABAC 混合:角色控制菜单访问,ABAC 控制数据行级权限。

策略引擎怎么集成?

权限模型定好后,关键是把策略引擎插进系统架构。现在主流思路是 PDP(Policy Decision Point) 和 PEP(Policy Enforcement Point) 分离。

  • PEP:放在 API 网关或应用层,拦截请求,问 PDP “这个人能不能做这个操作?”
  • PDP:独立服务或组件,加载策略,计算结果(允许/拒绝),返回给 PEP
  • PAP(Policy Administration Point):管理界面,给管理员写策略
  • PIP(Policy Information Point):提供属性数据,比如从 LDAP 取用户部门,从 CMDB 取资源标签

我习惯用 Open Policy Agent(OPA) 做 PDP,配合 Rego 语言写策略。下面是一个简单的 Rego 策略示例,要求用户必须是研发部,访问时间在工作日内,且资源标签不能是“机密”:

package security.access

default allow = false

# 获取用户部门
user_dept := input.user.attributes.department
# 获取资源敏感标签
res_label := input.resource.metadata.label
# 当前时间(假设 PEP 传入 UTC 小时)
current_hour := time.now_ns() / (1000 * 1000 * 1000) % 86400 / 3600

allow {
    user_dept == "R&D"
    current_hour >= 9
    current_hour < 18
    res_label != "confidential"
    # 工作日判断省略…
}

这个策略在 OPA 里跑起来就是一个 HTTP 接口。PEP(比如 NGINX 网关)发 POST 请求,带上用户属性、资源属性、动作,OPA 返回 true/false。集成很简单,但属性数据的质量和实时性会直接影响判断。我曾经踩过坑:PIP 从老旧的 LDAP 拿部门信息,更新延迟 2 小时,导致新入职员工访问被拒。后来加了缓存和异步同步才解决。

架构层面怎么落?

下面这张图是我在一个实际项目中用的权限集成架构。PEP 部署在网关层,PDP 独立部署,PIP 从多个数据源聚合属性。

RBAC/ABAC 策略引擎集成架构 客户端 / 用户 发起请求 PEP (策略执行点) API网关 / 应用中间件 PDP (策略决策点) OPA / 自定义引擎 评估请求 PAP (策略管理) 管理员控制台 下发策略 PIP (策略信息点) 属性聚合服务 请求属性 LDAP / AD CMDB / 资源库 环境感知服务 允许/拒绝 集成要点 • PEP 无状态,只做拦截转发 • PDP 无状态,可水平扩展 • PIP 需缓存属性避免级联故障 • 策略变更通过 PAP 推送,无重启

实际集成时,PEP 和 PDP 之间走的是同步 HTTP 还是异步 gRPC,取决于性能要求。我个人偏向同步,因为权限判定必须实时返回,不能等。但要注意超时:如果 PDP 挂掉,PEP 得有一个 fallback 策略(比如静默拒绝或缓存最近结果)。

混合模型怎么设计?

你想想看,完全用 ABAC 取代 RBAC 并不划算。大部分场景下,用户的角色仍然是最强信号。我推荐分层权限模型:

  1. 第一层:粗粒度 RBAC — 控制功能模块、菜单、大操作(增删改查)。
  2. 第二层:细粒度 ABAC — 控制数据行、字段、特定条件(比如仅本人创建的数据)。

策略引擎里怎么融合?很简单:先判断 RBAC 角色有没有权限,如果有,再交给 ABAC 策略做二次过滤。效率上,大部分请求在第一层就会因为角色不匹配直接拒绝,不会走到 ABAC 属性解析。

现实案例

我做过的一个医疗信息系统就是这样:医生角色可以访问患者病历,但 ABAC 策略进一步限制——只能访问自己负责的患者。如果只靠 RBAC,就得每个医生挂一个专属角色,那角色数就变成医生数了,显然不现实。

避坑总结

  • 属性数据质量是第一命门。PIP 拿到的属性不准,策略再完美也没用。我以前吃过亏,用户离职后 LDAP 没及时更新,导致已离职员工还能用旧角色访问系统。
  • 策略粒度别太细。我曾见过有人把每个文件的每个字段都写成一条 ABAC 策略,策略库上万条,性能惨不忍睹。保持“够用即可”原则。
  • 审计日志必须包含属性上下文。RBAC 只要记“用户+角色+操作”就行,ABAC 还得记“当时用户属性、资源属性、环境属性”,否则事后排查根本看不懂为什么拒绝。
  • 灰度切换。从 RBAC 迁移到 ABAC 不要一刀切。我建议先让新功能走 ABAC,老功能维持 RBAC,同时接入双写审计,比较两套模型的结果差异,确认无误后再全量切。

权限模型不是非此即彼,RBAC 和 ABAC 是战友不是对手。关键是结合你的业务场景、属性丰富度、性能要求,设计出刚好能用的方案。