星河

醉后不知天在水,满船清梦压星河

RBAC 和 ABAC 的理解

mingzaily / 2025-04-28


定义

RBAC

Role-Based Access Control - 基于角色的访问控制

ABAC

Attribute-Based Access Control - 基于属性的访问控制

ABAC 是不是指权限的属性?

RBAC 和 ABAC 结合

RBAC 和 ABAC 可以很好地结合,形成更强大、更灵活的访问控制模型。通常结合方式如下:

  1. RBAC 作为基础/粗粒度控制: 首先,通过 RBAC 确定用户是否具备执行某类操作的基本资格。用户的角色决定了他“可能”拥有的权限集。

    • 检查:这个人有没有这个角色? -> 这个角色有没有(基础的)这个权限?
  2. ABAC 作为补充/细粒度/动态控制: 在 RBAC 检查通过后(即用户具备基础权限),再通过 ABAC 策略进行进一步的、基于上下文的判断。

    • 检查:在当前的主体属性、资源属性、操作属性和环境属性下,是否有 ABAC 策略允许这个具体的操作?

结合后的检查逻辑:

查看这个人有没有这个角色,并且这个角色有没有这个权限 (RBAC),同时 (AND) 再看下这个人/资源/环境等属性是否符合允许该操作的 ABAC 策略?

例子:

这种结合方式利用了 RBAC 的管理简便性和 ABAC 的灵活性、动态性。

伪代码

我们使用 golang 和 casbin 来演示

1. Model (model.conf)

这个模型定义了请求的结构、策略的结构、角色定义、策略效果以及最重要的——匹配器(matcher),匹配器中包含了 RBAC 和 ABAC 的组合逻辑。

[request_definition]
r = sub, obj, act  # 请求:主体(Subject), 客体(Object), 操作(Action)

[policy_definition]
p = sub, obj_type, act # 策略:主体(这里用作角色Role), 对象类型, 操作

[role_definition]
g = _, _  # 角色继承/用户角色分配:用户, 角色

[policy_effect]
e = some(where (p.eft == allow)) # 效果:只要有一条策略允许,就允许

[matchers]
m = g(r.sub.ID, p.sub) && \      # RBAC检查:请求的用户(r.sub.ID) 是否拥有策略中定义的角色(p.sub)?
    r.obj.Type == p.obj && \     # 基础检查:请求的对象类型(r.obj.Type) 是否匹配策略中的对象类型(p.obj)?
    r.act == p.act && \          # 基础检查:请求的操作(r.act) 是否匹配策略中的操作(p.act)?
    r.act == "edit" && \         # ABAC条件:操作必须是 "edit"?
    r.obj.Status == "draft" && \ # ABAC条件:对象的状态必须是 "draft"?
    ( \                          # ABAC条件:满足以下任一条件
        r.sub.ID == r.obj.AuthorID || \             # 条件1:请求的用户(r.sub.ID) 是对象的作者(r.obj.AuthorID)?
        (r.sub.Title == "manager" && r.sub.Dept == r.obj.Dept) \ # 条件2:请求的用户头衔是 "manager" 并且 用户的部门(r.sub.Dept) 与对象的部门(r.obj.Dept) 相同?
    )

关键点解释:

==关于 ABAC 的表示,可以通过自定义函数完成==

2. Policy (policy.csv)

这个文件定义了具体的角色分配和基础权限。

# RBAC 基础权限: "editor" 角色可以 "edit" "document" 类型的对象
p, editor, document, edit

# 用户角色分配
g, alice, editor
g, bob, editor
g, charlie, editor # 假设经理也需要 editor 角色才有基础编辑权
g, charlie, manager # 查理同时也是经理

3. Go 伪代码 (main.go)

package main

import (
	"fmt"
	"log"

	"github.com/casbin/casbin/v2"
)

// 主体(用户)结构体,包含属性
type Subject struct {
	ID    string
	Title string
	Dept  string
}

// 客体(文档)结构体,包含属性
type Object struct {
	ID       string
	Type     string // 对象类型,匹配 model 中的 obj_type
	Status   string
	AuthorID string
	Dept     string
}

func main() {
	// 从文件加载模型和策略
	enforcer, err := casbin.NewEnforcer("model.conf", "policy.csv")
	if err != nil {
		log.Fatalf("Failed to create enforcer: %v", err)
	}

	// --- 定义测试数据 ---
	// 用户
	alice := Subject{ID: "alice", Title: "developer", Dept: "Tech"}
	bob := Subject{ID: "bob", Title: "developer", Dept: "Tech"}
	charlie := Subject{ID: "charlie", Title: "manager", Dept: "Sales"} // Sales 部门经理
	dave := Subject{ID: "dave", Title: "manager", Dept: "Tech"}     // Tech 部门经理

	// 文档
	doc1 := Object{ID: "doc1", Type: "document", Status: "draft", AuthorID: "alice", Dept: "Tech"}
	doc2 := Object{ID: "doc2", Type: "document", Status: "draft", AuthorID: "bob", Dept: "Sales"}
	doc3 := Object{ID: "doc3", Type: "document", Status: "published", AuthorID: "alice", Dept: "Tech"} // 已发布

	// --- 执行权限检查 ---
	action := "edit"

	// 场景 1: Alice (作者) 编辑自己的草稿 doc1 (Tech 部门) -> 应该允许 (作者规则)
	check(enforcer, alice, doc1, action)

	// 场景 2: Bob (非作者, Tech 部门普通员工) 编辑 Alice 的草稿 doc1 -> 应该拒绝
	check(enforcer, bob, doc1, action)

	// 场景 3: Charlie (Sales 经理) 编辑 Bob 的草稿 doc2 (Sales 部门) -> 应该允许 (同部门经理规则)
	check(enforcer, charlie, doc2, action)

	// 场景 4: Charlie (Sales 经理) 编辑 Alice 的草稿 doc1 (Tech 部门) -> 应该拒绝 (不同部门)
	check(enforcer, charlie, doc1, action)

	// 场景 5: Dave (Tech 经理) 编辑 Alice 的草稿 doc1 (Tech 部门) -> 应该允许 (同部门经理规则)
	check(enforcer, dave, doc1, action)

	// 场景 6: Alice (作者) 编辑自己已发布的 doc3 -> 应该拒绝 (状态不是 draft)
	check(enforcer, alice, doc3, action)

	// 场景 7: Alice (作者) 尝试 "view" 自己的草稿 doc1 -> 应该拒绝 (因为基础策略 p 只有 edit, 且 matcher 强制 act=="edit")
	// 注意:如果需要 view 权限,需要单独添加策略和可能的 matcher 逻辑
	check(enforcer, alice, doc1, "view")

}

// 辅助函数,执行检查并打印结果
func check(e *casbin.Enforcer, sub Subject, obj Object, act string) {
	allowed, err := e.Enforce(sub, obj, act)
	if err != nil {
		fmt.Printf("Error checking permission for %s to %s %s (%s): %v\n", sub.ID, act, obj.ID, obj.Status, err)
		return
	}
	if allowed {
		fmt.Printf("Allow: %s (%s, %s) can %s %s (%s, Author:%s, Dept:%s)\n", sub.ID, sub.Title, sub.Dept, act, obj.ID, obj.Status, obj.AuthorID, obj.Dept)
	} else {
		fmt.Printf("Deny:  %s (%s, %s) cannot %s %s (%s, Author:%s, Dept:%s)\n", sub.ID, sub.Title, sub.Dept, act, obj.ID, obj.Status, obj.AuthorID, obj.Dept)
	}
}

运行结果预期:

Allow: alice (developer, Tech) can edit doc1 (draft, Author:alice, Dept:Tech)
Deny:  bob (developer, Tech) cannot edit doc1 (draft, Author:alice, Dept:Tech)
Allow: charlie (manager, Sales) can edit doc2 (draft, Author:bob, Dept:Sales)
Deny:  charlie (manager, Sales) cannot edit doc1 (draft, Author:alice, Dept:Tech)
Allow: dave (manager, Tech) can edit doc1 (draft, Author:alice, Dept:Tech)
Deny:  alice (developer, Tech) cannot edit doc3 (published, Author:alice, Dept:Tech)
Deny:  alice (developer, Tech) cannot view doc1 (draft, Author:alice, Dept:Tech)

这个例子展示了如何在 Casbin 中:

  1. 使用 g 定义用户角色 (RBAC)。
  2. 使用 p 定义角色对应的基础权限 (RBAC)。
  3. matcher 中结合 g(...) 的 RBAC 检查和直接访问请求对象属性 (r.sub.*, r.obj.*) 的 ABAC 检查,来实现复杂的、基于属性的访问控制逻辑。

总结

RBAC 的抽象与动态性

ABAC 的定制化与实现

因此在典型的 Casbin 应用中: