Files
cs-note/hhs/MS/02-服务治理/09-网关鉴权策略/RBAC权限模型实战.md
T

285 lines
12 KiB
Markdown
Raw Normal View History

2026-05-24 11:42:38 +08:00
---
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 + 条件<br/>代码内嵌"]
S2 -->|"规则超过 50 条"| S3["L3 RBAC + OPA<br/>策略独立"]
S3 -->|"跨系统统一管理"| S4["L4 全量 ABAC<br/>多租户动态"]
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-网关鉴权策略]] — 网关层与业务层的权限分工