返回博客

ReBAC 实现:基于关系的访问控制

ReBAC(Relationship-Based Access Control)是 coomia-dip 三层权限模型的第三层,也是最强大的一层。它通过对象之间的图关系来推导访问权限,天然契合本体论驱动的平台架构。本文详解 ReBAC 的关系图模型、Zanzibar 风格的元组存储、关系遍历算法、权限继承与否定、性能优化策略,以及与 RBAC/ABAC 层的协同决策机制。

Coomia发布于 2025年9月17日20 分钟阅读
分享本文Twitter / X

系列:S6 平台工程 · 第 4 篇 | 难度:高级 | 阅读时间:18 分钟

ReBAC 实现:基于关系的访问控制

#TL;DR

ReBAC(Relationship-Based Access Control)是 coomia-dip 三层权限模型的第三层,也是最强大的一层。它通过对象之间的图关系来推导访问权限,天然契合本体论驱动的平台架构。本文详解 ReBAC 的关系图模型、Zanzibar 风格的元组存储、关系遍历算法、权限继承与否定、性能优化策略,以及与 RBAC/ABAC 层的协同决策机制。

#1. 为什么需要 ReBAC

#1.1 RBAC 和 ABAC 的局限

在大规模多租户平台中,RBAC 和 ABAC 各有盲区:

场景RBAC 能力ABAC 能力ReBAC 能力
用户 A 是文件夹 X 的 owner需创建专用角色可通过属性匹配直接关系查询
用户 A 属于组 G,组 G 有权访问项目 P需角色传递不支持关系链遍历
项目 P 嵌套在组织 O 中,O 的管理员能管理 P需层级角色不支持层级关系继承
文件 F 在文件夹 D 中,D 在项目 P 下需为每个文件分配角色需维护属性自动层级继承
共享链接使特定用户获得只读权限需专门角色需专门策略直接关系

ReBAC 的核心洞察是:权限本质上是对象之间的关系,而本体论平台天然维护着丰富的对象关系图。

#1.2 ReBAC 与 Ontology 的天然契合

coomia-dip 的 Ontology 已经定义了对象类型和关系类型。ReBAC 复用这些关系来推导权限:

Code
Ontology 层定义:
  ObjectType: Project
  ObjectType: Dataset
  RelationType: Project --contains--> Dataset

ReBAC 权限语义:
  Project:proj1 --editor--> User:alice
  Project:proj1 --contains--> Dataset:ds1
  => User:alice 对 Dataset:ds1 有 editor 权限(通过关系继承)

#1.3 Google Zanzibar 的启发

Google Zanzibar 论文(2019)定义了 ReBAC 的工业标准实现。coomia-dip 借鉴其核心思想:

  • 元组存储(object, relation, user) 三元组
  • 命名空间配置:定义类型和关系的 schema
  • Check API:判定用户是否有权
  • Expand API:展开某关系的所有用户
  • Watch API:订阅权限变更

#2. 关系图模型设计

#2.1 核心数据结构

ReBAC 的基础是关系元组(Relationship Tuple):

PROTOBUF
// 关系元组定义
message RelationshipTuple {
  ObjectReference resource = 1;    // 资源对象
  string relation = 2;            // 关系名称
  SubjectReference subject = 3;    // 主体(用户或对象集合)
}

message ObjectReference {
  string type = 1;     // 对象类型,如 "project", "dataset"
  string id = 2;       // 对象 ID
}

message SubjectReference {
  ObjectReference object = 1;      // 主体对象
  string optional_relation = 2;    // 可选的关系限定
}

关键设计:SubjectReference 的 optional_relation

这允许表达"对象集合"作为主体,例如:

Code
(document:readme, viewer, project:alpha#member)

含义:project:alpha 的所有 member 都是 document:readme 的 viewer。

#2.2 命名空间配置(Type Definition)

每个对象类型需要定义其关系模式:

YAML
# 命名空间配置 — 类似 Zanzibar 的 namespace config
type_definitions:
  - type: "organization"
    relations:
      admin:
        description: "组织管理员"
        directly_related_types:
          - type: "user"
      member:
        description: "组织成员"
        union:
          - directly_related: { type: "user" }
          - computed_relation: "admin"  # admin 也是 member

  - type: "project"
    relations:
      parent_org:
        description: "所属组织"
        directly_related_types:
          - type: "organization"
      admin:
        description: "项目管理员"
        union:
          - directly_related: { type: "user" }
          - tuple_to_userset:
              tupleset: "parent_org"
              computed_relation: "admin"  # 组织 admin 也是项目 admin
      editor:
        description: "项目编辑者"
        union:
          - directly_related: { type: "user" }
          - directly_related: { type: "team", relation: "member" }
          - computed_relation: "admin"  # admin 也是 editor
      viewer:
        description: "项目查看者"
        union:
          - directly_related: { type: "user" }
          - computed_relation: "editor"  # editor 也是 viewer

  - type: "dataset"
    relations:
      parent_project:
        description: "所属项目"
        directly_related_types:
          - type: "project"
      owner:
        description: "数据集拥有者"
        directly_related: { type: "user" }
      editor:
        union:
          - directly_related: { type: "user" }
          - computed_relation: "owner"
          - tuple_to_userset:
              tupleset: "parent_project"
              computed_relation: "editor"
      viewer:
        union:
          - directly_related: { type: "user" }
          - computed_relation: "editor"
          - tuple_to_userset:
              tupleset: "parent_project"
              computed_relation: "viewer"

#2.3 关系的三种推导方式

coomia-dip 的 ReBAC 引擎支持三种关系推导:

Code
1. 直接关系 (Direct Relation)
   ┌─────────┐   viewer   ┌──────────┐
   │ User:bob │──────────>│ Doc:file1 │
   └─────────┘            └──────────┘

2. 计算关系 (Computed Relation / Implied)
   定义: viewer 包含 editor
   ┌───────────┐   editor   ┌──────────┐
   │ User:alice │──────────>│ Doc:file1 │
   └───────────┘            └──────────┘
   => alice 自动获得 viewer 权限

3. 元组到用户集 (Tuple-to-Userset)
   ┌──────────┐   parent   ┌──────────────┐
   │ Doc:file1 │──────────>│ Project:proj1 │
   └──────────┘            └──────────────┘
                                  │ editor
                                  ▼
                           ┌───────────┐
                           │ User:carol │
                           └───────────┘
   => carol 通过 proj1 的 editor 关系继承 file1 的 editor 权限

#3. 元组存储引擎

#3.1 存储层设计

关系元组的存储需要高吞吐读写和快速查询:

Code
┌─────────────────────────────────────────────────────────┐
│                  Tuple Store Layer                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────┐   ┌─────────────┐   ┌──────────────┐ │
│  │  Write Path  │   │  Read Path  │   │  Watch Path  │ │
│  │  (gRPC API)  │   │  (gRPC API) │   │ (Stream API) │ │
│  └──────┬──────┘   └──────┬──────┘   └──────┬───────┘ │
│         │                  │                  │         │
│  ┌──────▼──────────────────▼──────────────────▼───────┐ │
│  │              Changelog (WAL)                       │ │
│  │  - Zookie/Token per write for consistency          │ │
│  │  - Ordered sequence of tuple changes               │ │
│  └──────┬────────────────────────────────────────────┘ │
│         │                                               │
│  ┌──────▼────────────────────────────────────────────┐ │
│  │              Primary Store (PostgreSQL)            │ │
│  │  - relation_tuples table                          │ │
│  │  - Indexed: (resource_type, resource_id, relation)│ │
│  │  - Indexed: (subject_type, subject_id)            │ │
│  └───────────────────────────────────────────────────┘ │
│                                                         │
│  ┌───────────────────────────────────────────────────┐ │
│  │              Cache Layer (Redis)                   │ │
│  │  - Check result cache with Zookie invalidation    │ │
│  │  - Expand result cache                            │ │
│  └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

#3.2 数据库表设计

SQL
-- 核心关系元组表
CREATE TABLE relation_tuples (
    id              BIGSERIAL PRIMARY KEY,
    resource_type   VARCHAR(128) NOT NULL,
    resource_id     VARCHAR(256) NOT NULL,
    relation        VARCHAR(64)  NOT NULL,
    subject_type    VARCHAR(128) NOT NULL,
    subject_id      VARCHAR(256) NOT NULL,
    subject_relation VARCHAR(64),  -- NULL 表示直接用户
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    deleted_at      TIMESTAMP WITH TIME ZONE,  -- 软删除
    zookie          BIGINT NOT NULL,  -- 一致性令牌

    UNIQUE (resource_type, resource_id, relation,
            subject_type, subject_id, subject_relation)
);

-- 高频查询索引
CREATE INDEX idx_tuples_resource ON relation_tuples
    (resource_type, resource_id, relation)
    WHERE deleted_at IS NULL;

CREATE INDEX idx_tuples_subject ON relation_tuples
    (subject_type, subject_id)
    WHERE deleted_at IS NULL;

CREATE INDEX idx_tuples_zookie ON relation_tuples (zookie);

-- 变更日志表(用于 Watch API 和一致性)
CREATE TABLE changelog (
    zookie          BIGSERIAL PRIMARY KEY,
    operation       VARCHAR(16) NOT NULL,  -- TOUCH / DELETE
    resource_type   VARCHAR(128) NOT NULL,
    resource_id     VARCHAR(256) NOT NULL,
    relation        VARCHAR(64)  NOT NULL,
    subject_type    VARCHAR(128) NOT NULL,
    subject_id      VARCHAR(256) NOT NULL,
    subject_relation VARCHAR(64),
    timestamp       TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

#3.3 一致性模型:Zookie

Zanzibar 引入 Zookie 解决"新敌人"问题(权限刚被撤销但缓存尚未更新):

Code
写入流程:
  1. 客户端写入元组 → 获得 zookie Z₁
  2. 后续 Check 请求携带 zookie Z₁
  3. 服务端确保评估时看到 Z₁ 及之前的所有写入

一致性级别:
  - minimize_latency: 使用缓存,可能读到旧数据
  - at_least_as_fresh(zookie): 至少和指定 zookie 一样新
  - fully_consistent: 读最新快照,最慢但最安全
Python
class ZookieManager:
    """Zookie 一致性令牌管理器"""

    def __init__(self, store: TupleStore):
        self._store = store
        self._current_zookie = AtomicCounter()

    def next_zookie(self) -> int:
        """生成下一个全局递增的 zookie"""
        return self._current_zookie.increment()

    def snapshot_at(self, zookie: int) -> TupleSnapshot:
        """获取指定 zookie 时刻的元组快照"""
        return self._store.snapshot(zookie)

    def is_at_least_as_fresh(self, requested: int, current: int) -> bool:
        """检查当前快照是否满足一致性要求"""
        return current >= requested

#4. 权限检查算法

#4.1 Check API 实现

Check API 是 ReBAC 的核心:判定 (user, permission, resource) 是否成立。

Python
class ReBACheckEngine:
    """ReBAC 权限检查引擎"""

    def __init__(self, store: TupleStore, type_system: TypeSystem):
        self._store = store
        self._type_system = type_system
        self._cache = CheckCache()

    async def check(
        self,
        resource: ObjectRef,
        permission: str,
        subject: SubjectRef,
        consistency: Consistency = Consistency.MINIMIZE_LATENCY,
    ) -> CheckResult:
        """
        检查 subject 是否对 resource 拥有指定 permission。

        算法思路:
        1. 获取 resource 类型的关系定义
        2. 根据定义的推导规则递归展开
        3. 在展开过程中搜索 subject 是否可达
        """
        cache_key = (resource, permission, subject)
        if cached := self._cache.get(cache_key, consistency):
            return cached

        type_def = self._type_system.get_type(resource.type)
        relation_def = type_def.get_relation(permission)

        result = await self._evaluate_rewrite(
            resource, relation_def.rewrite, subject,
            visited=set(), depth=0
        )

        self._cache.set(cache_key, result)
        return result

    async def _evaluate_rewrite(
        self,
        resource: ObjectRef,
        rewrite: RewriteRule,
        subject: SubjectRef,
        visited: set,
        depth: int,
    ) -> CheckResult:
        """递归评估重写规则"""
        if depth > MAX_DEPTH:
            raise MaxDepthExceededError()

        cycle_key = (resource, rewrite, subject)
        if cycle_key in visited:
            return CheckResult.DENIED  # 防止循环
        visited.add(cycle_key)

        match rewrite:
            case DirectRelation():
                return await self._check_direct(resource, rewrite.relation, subject)

            case ComputedRelation(relation=rel):
                # 权限蕴含:如 editor 蕴含 viewer
                rel_def = self._type_system.get_relation(resource.type, rel)
                return await self._evaluate_rewrite(
                    resource, rel_def.rewrite, subject, visited, depth + 1
                )

            case TupleToUserset(tupleset=ts, computed=comp):
                # 查找所有满足 tupleset 关系的父对象
                parents = await self._store.query_tuples(
                    resource_type=resource.type,
                    resource_id=resource.id,
                    relation=ts,
                )
                for parent_tuple in parents:
                    parent_ref = ObjectRef(
                        type=parent_tuple.subject_type,
                        id=parent_tuple.subject_id
                    )
                    parent_type_def = self._type_system.get_type(parent_ref.type)
                    parent_rel_def = parent_type_def.get_relation(comp)
                    result = await self._evaluate_rewrite(
                        parent_ref, parent_rel_def.rewrite, subject,
                        visited, depth + 1
                    )
                    if result.allowed:
                        return result
                return CheckResult.DENIED

            case Union(children=children):
                # 任一子规则允许即允许
                tasks = [
                    self._evaluate_rewrite(resource, child, subject, visited.copy(), depth + 1)
                    for child in children
                ]
                results = await asyncio.gather(*tasks)
                return CheckResult.ALLOWED if any(r.allowed for r in results) else CheckResult.DENIED

            case Intersection(children=children):
                # 所有子规则都允许才允许
                tasks = [
                    self._evaluate_rewrite(resource, child, subject, visited.copy(), depth + 1)
                    for child in children
                ]
                results = await asyncio.gather(*tasks)
                return CheckResult.ALLOWED if all(r.allowed for r in results) else CheckResult.DENIED

            case Exclusion(base=base, subtract=sub):
                # base 允许且 subtract 不允许
                base_result = await self._evaluate_rewrite(
                    resource, base, subject, visited.copy(), depth + 1
                )
                if not base_result.allowed:
                    return CheckResult.DENIED
                sub_result = await self._evaluate_rewrite(
                    resource, sub, subject, visited.copy(), depth + 1
                )
                return CheckResult.ALLOWED if not sub_result.allowed else CheckResult.DENIED

#4.2 Expand API 实现

Expand API 展开某个关系的所有主体,用于调试和审计:

Python
async def expand(
    self,
    resource: ObjectRef,
    relation: str,
) -> UsersetTree:
    """
    展开指定资源和关系的完整用户集合树。
    返回树形结构,叶子节点是具体用户。
    """
    type_def = self._type_system.get_type(resource.type)
    rel_def = type_def.get_relation(relation)

    return await self._expand_rewrite(resource, rel_def.rewrite, visited=set())

async def _expand_rewrite(
    self,
    resource: ObjectRef,
    rewrite: RewriteRule,
    visited: set,
) -> UsersetTree:
    match rewrite:
        case DirectRelation():
            tuples = await self._store.query_tuples(
                resource_type=resource.type,
                resource_id=resource.id,
                relation=rewrite.relation,
            )
            leaf_users = []
            intermediate_nodes = []
            for t in tuples:
                if t.subject_relation:
                    # 主体是用户集合,继续展开
                    child_tree = await self._expand_rewrite(
                        ObjectRef(t.subject_type, t.subject_id),
                        DirectRelation(t.subject_relation),
                        visited,
                    )
                    intermediate_nodes.append(child_tree)
                else:
                    leaf_users.append(SubjectRef(t.subject_type, t.subject_id))
            return UsersetTree(
                type="leaf" if not intermediate_nodes else "union",
                users=leaf_users,
                children=intermediate_nodes,
            )

        case Union(children=children):
            child_trees = []
            for child in children:
                tree = await self._expand_rewrite(resource, child, visited)
                child_trees.append(tree)
            return UsersetTree(type="union", children=child_trees)

#4.3 并行评估与短路优化

Python
class ParallelCheckOptimizer:
    """并行评估优化器"""

    async def check_with_early_exit(
        self,
        resource: ObjectRef,
        union_rules: list[RewriteRule],
        subject: SubjectRef,
    ) -> CheckResult:
        """
        对 Union 规则使用 asyncio 并发评估,
        任一返回 ALLOWED 即短路退出。
        """
        done_event = asyncio.Event()
        result_holder = {"result": CheckResult.DENIED}

        async def check_branch(rule: RewriteRule):
            r = await self._evaluate_rewrite(resource, rule, subject)
            if r.allowed:
                result_holder["result"] = CheckResult.ALLOWED
                done_event.set()

        tasks = [asyncio.create_task(check_branch(r)) for r in union_rules]

        # 等待任一任务设置事件或所有任务完成
        wait_task = asyncio.create_task(done_event.wait())
        all_done = asyncio.gather(*tasks, return_exceptions=True)

        await asyncio.wait(
            [wait_task, all_done],
            return_when=asyncio.FIRST_COMPLETED,
        )

        # 取消剩余任务
        for t in tasks:
            if not t.done():
                t.cancel()

        return result_holder["result"]

#5. 关系继承与否定

#5.1 层级关系继承

在 coomia-dip 中,对象的层级关系决定了权限继承路径:

Code
Organization: acme
    │ admin: [alice]
    │ member: [bob, charlie]
    │
    ├── Project: alpha
    │   │ parent_org: acme
    │   │ admin: [inherited from org:acme#admin → alice]
    │   │ editor: [dave]
    │   │ viewer: [eve]
    │   │
    │   ├── Dataset: sales_2024
    │   │   parent_project: alpha
    │   │   owner: [frank]
    │   │   editor: [inherited from project:alpha#editor → dave]
    │   │   viewer: [inherited from project:alpha#viewer → eve]
    │   │
    │   └── Dataset: customer_info
    │       parent_project: alpha
    │       owner: [grace]
    │       editor: [inherited]
    │       viewer: [inherited]
    │
    └── Project: beta
        │ parent_org: acme
        │ admin: [inherited from org:acme#admin → alice]
        │ editor: [heidi]

#5.2 否定关系(Exclusion)

有时需要从继承的权限中排除特定用户:

YAML
# 敏感数据集:继承项目权限但排除外包人员
type: "sensitive_dataset"
relations:
  parent_project:
    directly_related_types: [{ type: "project" }]
  blocked:
    directly_related_types: [{ type: "user" }]
  viewer:
    exclusion:
      base:
        tuple_to_userset:
          tupleset: "parent_project"
          computed_relation: "viewer"
      subtract:
        direct_relation: "blocked"

#5.3 条件关系(Contextual Tuples)

某些关系只在特定上下文中成立:

Python
class ContextualTuple:
    """上下文感知的关系元组"""

    resource: ObjectRef
    relation: str
    subject: SubjectRef
    condition: dict  # 条件表达式

    def evaluate_condition(self, context: dict) -> bool:
        """评估条件是否满足"""
        for key, expected in self.condition.items():
            actual = context.get(key)
            if actual != expected:
                return False
        return True

# 使用示例:工作时间内才有编辑权限
contextual_tuple = ContextualTuple(
    resource=ObjectRef("project", "alpha"),
    relation="editor",
    subject=SubjectRef("user", "bob"),
    condition={"time_range": "business_hours", "network": "internal"},
)

#6. 与 RBAC/ABAC 的协同

#6.1 三层决策流水线

coomia-dip 的权限决策遵循三层流水线:

Code
请求进入
    │
    ▼
┌─────────────────────────────────┐
│ Layer 1: RBAC 粗粒度过滤         │
│ - 检查用户角色是否包含所需权限     │
│ - 快速拒绝无角色匹配的请求        │
│ Result: ALLOW / DENY / CONTINUE  │
└──────────────┬──────────────────┘
               │ CONTINUE
               ▼
┌─────────────────────────────────┐
│ Layer 2: ABAC 属性条件评估        │
│ - 评估主体/资源/环境属性条件       │
│ - 检查数据分类级别约束            │
│ Result: ALLOW / DENY / CONTINUE  │
└──────────────┬──────────────────┘
               │ CONTINUE
               ▼
┌─────────────────────────────────┐
│ Layer 3: ReBAC 关系图遍历         │
│ - 检查对象间的直接和间接关系       │
│ - 处理继承、否定、条件关系         │
│ Result: ALLOW / DENY             │
└─────────────────────────────────┘

#6.2 决策合并策略

Python
class UnifiedPolicyEngine:
    """统一策略引擎:合并 RBAC + ABAC + ReBAC"""

    async def authorize(self, request: AuthzRequest) -> AuthzDecision:
        # Phase 1: RBAC
        rbac_result = await self.rbac_engine.check(
            user=request.subject,
            permission=request.permission,
        )
        if rbac_result == Decision.EXPLICIT_DENY:
            return AuthzDecision.DENIED
        if rbac_result == Decision.EXPLICIT_ALLOW and not request.needs_fine_grained:
            return AuthzDecision.ALLOWED

        # Phase 2: ABAC
        abac_result = await self.abac_engine.evaluate(
            subject_attrs=request.subject_attributes,
            resource_attrs=request.resource_attributes,
            action=request.action,
            environment=request.environment,
        )
        if abac_result == Decision.EXPLICIT_DENY:
            return AuthzDecision.DENIED

        # Phase 3: ReBAC
        rebac_result = await self.rebac_engine.check(
            resource=request.resource,
            permission=request.permission,
            subject=request.subject_ref,
        )

        # 合并决策
        return self._merge_decisions(rbac_result, abac_result, rebac_result)

    def _merge_decisions(self, rbac, abac, rebac) -> AuthzDecision:
        """
        合并策略:
        - 任一 EXPLICIT_DENY → DENIED(deny 优先)
        - ReBAC ALLOW 且 ABAC 无否定 → ALLOWED
        - RBAC ALLOW 且 ABAC ALLOW → ALLOWED
        - 默认 → DENIED(默认拒绝)
        """
        if any(d == Decision.EXPLICIT_DENY for d in [rbac, abac, rebac]):
            return AuthzDecision.DENIED
        if rebac.allowed and abac != Decision.EXPLICIT_DENY:
            return AuthzDecision.ALLOWED
        if rbac == Decision.EXPLICIT_ALLOW and abac == Decision.EXPLICIT_ALLOW:
            return AuthzDecision.ALLOWED
        return AuthzDecision.DENIED

#7. 性能优化

#7.1 缓存策略

ReBAC 的图遍历成本高,必须有效缓存:

Python
class ReBACheckCache:
    """分层缓存系统"""

    def __init__(self):
        self.l1_local = LRUCache(max_size=10000)     # 进程内缓存
        self.l2_redis = RedisCache(ttl_seconds=300)   # 分布式缓存

    def get(self, key: CheckCacheKey, consistency: Consistency) -> Optional[CheckResult]:
        if consistency == Consistency.FULLY_CONSISTENT:
            return None  # 不使用缓存

        if consistency == Consistency.AT_LEAST_AS_FRESH:
            # 检查缓存的 zookie 是否满足要求
            cached = self.l1_local.get(key)
            if cached and cached.zookie >= consistency.min_zookie:
                return cached.result
            cached = self.l2_redis.get(key)
            if cached and cached.zookie >= consistency.min_zookie:
                self.l1_local.set(key, cached)
                return cached.result
            return None

        # minimize_latency: 直接返回缓存
        if result := self.l1_local.get(key):
            return result.result
        if result := self.l2_redis.get(key):
            self.l1_local.set(key, result)
            return result.result
        return None

    def invalidate_for_resource(self, resource: ObjectRef):
        """当资源的关系发生变更时,失效相关缓存"""
        pattern = f"check:{resource.type}:{resource.id}:*"
        self.l1_local.invalidate_pattern(pattern)
        self.l2_redis.invalidate_pattern(pattern)

#7.2 图遍历深度限制

Python
# 全局配置
MAX_TRAVERSAL_DEPTH = 15        # 最大递归深度
MAX_CONCURRENT_BRANCHES = 50    # 最大并发分支数
CHECK_TIMEOUT_MS = 500          # 单次 Check 超时

class DepthLimiter:
    """深度限制器,防止无限递归"""

    def __init__(self, max_depth: int = MAX_TRAVERSAL_DEPTH):
        self.max_depth = max_depth
        self._metrics = MetricsCollector()

    def check_depth(self, current_depth: int, context: str):
        if current_depth > self.max_depth:
            self._metrics.increment("rebac.depth_exceeded", tags={"context": context})
            raise MaxDepthExceededError(
                f"ReBAC traversal exceeded max depth {self.max_depth} at {context}"
            )

#7.3 批量 Check 优化

Python
class BatchCheckOptimizer:
    """批量权限检查优化"""

    async def batch_check(
        self,
        checks: list[CheckRequest],
    ) -> list[CheckResult]:
        """
        批量检查优化策略:
        1. 去重:相同的 (resource, permission, subject) 只查一次
        2. 预取:批量加载相关元组到缓存
        3. 共享遍历:复用中间遍历结果
        """
        # 去重
        unique_checks = {c.cache_key: c for c in checks}

        # 预取所有涉及的资源的元组
        resources = {(c.resource.type, c.resource.id) for c in unique_checks.values()}
        await self._prefetch_tuples(resources)

        # 并发执行去重后的检查
        tasks = {
            key: asyncio.create_task(self.engine.check(c.resource, c.permission, c.subject))
            for key, c in unique_checks.items()
        }
        results = {key: await task for key, task in tasks.items()}

        # 映射回原始请求顺序
        return [results[c.cache_key] for c in checks]

    async def _prefetch_tuples(self, resources: set[tuple[str, str]]):
        """批量预取元组到缓存"""
        tasks = [
            self._store.prefetch(resource_type=rt, resource_id=ri)
            for rt, ri in resources
        ]
        await asyncio.gather(*tasks)

#8. gRPC API 设计

#8.1 服务定义

PROTOBUF
service ReBAAuthorizationService {
    // 权限检查
    rpc Check(CheckRequest) returns (CheckResponse);
    rpc BatchCheck(BatchCheckRequest) returns (BatchCheckResponse);

    // 关系展开
    rpc Expand(ExpandRequest) returns (ExpandResponse);

    // 关系管理
    rpc WriteTuples(WriteTuplesRequest) returns (WriteTuplesResponse);
    rpc DeleteTuples(DeleteTuplesRequest) returns (DeleteTuplesResponse);
    rpc ReadTuples(ReadTuplesRequest) returns (ReadTuplesResponse);

    // 变更监听
    rpc Watch(WatchRequest) returns (stream WatchResponse);

    // 反向查询:用户能访问哪些资源
    rpc LookupResources(LookupResourcesRequest) returns (stream LookupResourcesResponse);
    rpc LookupSubjects(LookupSubjectsRequest) returns (stream LookupSubjectsResponse);
}

message CheckRequest {
    ObjectReference resource = 1;
    string permission = 2;
    SubjectReference subject = 3;
    Consistency consistency = 4;
    repeated RelationshipTuple contextual_tuples = 5;
}

message CheckResponse {
    bool allowed = 1;
    int64 checked_at_zookie = 2;
    DebugInfo debug = 3;  // 可选的调试信息
}

#8.2 LookupResources:反向查询

Python
async def lookup_resources(
    self,
    resource_type: str,
    permission: str,
    subject: SubjectRef,
) -> AsyncIterator[ObjectRef]:
    """
    查找 subject 对哪些 resource_type 类型的资源拥有 permission。
    这是 Check 的反向操作,用于列出用户可访问的资源。
    """
    # 策略1:从 subject 出发正向遍历
    direct_tuples = await self._store.query_by_subject(
        subject_type=subject.type,
        subject_id=subject.id,
    )

    seen = set()
    for tuple in direct_tuples:
        if tuple.resource_type == resource_type:
            # 验证该关系是否蕴含所需权限
            if await self._relation_implies(
                resource_type, tuple.relation, permission
            ):
                if tuple.resource_id not in seen:
                    seen.add(tuple.resource_id)
                    yield ObjectRef(resource_type, tuple.resource_id)

    # 策略2:考虑通过中间对象的间接关系
    for tuple in direct_tuples:
        # 查找以此对象为父对象的子资源
        children = await self._store.query_tuples(
            subject_type=tuple.resource_type,
            subject_id=tuple.resource_id,
        )
        for child in children:
            if child.resource_type == resource_type and child.resource_id not in seen:
                # 完整 Check 验证
                result = await self.check(
                    ObjectRef(child.resource_type, child.resource_id),
                    permission,
                    subject,
                )
                if result.allowed:
                    seen.add(child.resource_id)
                    yield ObjectRef(child.resource_type, child.resource_id)

#9. 测试与调试

#9.1 关系图可视化

Python
class ReBAGraphVisualizer:
    """关系图可视化工具,用于调试权限继承"""

    async def visualize_permissions(
        self,
        resource: ObjectRef,
        max_depth: int = 5,
    ) -> str:
        """生成 Mermaid 格式的关系图"""
        lines = ["graph TD"]
        visited = set()

        async def traverse(obj: ObjectRef, depth: int):
            if depth > max_depth or obj in visited:
                return
            visited.add(obj)
            node_id = f"{obj.type}_{obj.id}"

            tuples = await self._store.query_tuples(
                resource_type=obj.type,
                resource_id=obj.id,
            )
            for t in tuples:
                subject_id = f"{t.subject_type}_{t.subject_id}"
                label = t.relation
                if t.subject_relation:
                    label += f" (via #{t.subject_relation})"
                lines.append(f"    {subject_id} -->|{label}| {node_id}")

                if t.subject_type != "user":
                    await traverse(
                        ObjectRef(t.subject_type, t.subject_id),
                        depth + 1,
                    )

        await traverse(resource, 0)
        return "\n".join(lines)

#9.2 权限解释

Python
class PermissionExplainer:
    """权限决策解释器"""

    async def explain(
        self,
        resource: ObjectRef,
        permission: str,
        subject: SubjectRef,
    ) -> ExplanationTree:
        """
        解释为什么 subject 对 resource 有/没有 permission。
        返回完整的决策树,包括每一步的评估结果。
        """
        root = ExplanationNode(
            description=f"Check: {subject} -> {permission} -> {resource}",
        )

        result = await self._check_with_explanation(
            resource, permission, subject, root
        )

        root.result = result
        return ExplanationTree(root=root)

#10. 生产部署考量

#10.1 多区域部署

Code
┌─────────────────────────────────────────────┐
│              Region A (Primary)             │
│  ┌────────────┐     ┌────────────────────┐  │
│  │ ReBAC API  │────>│ PostgreSQL Primary │  │
│  │ (gRPC)     │     │ (Write + Read)     │  │
│  └────────────┘     └────────────────────┘  │
│        │                     │               │
│        │              WAL Replication         │
│        │                     │               │
└────────│─────────────────────│───────────────┘
         │                     │
         │              ┌──────▼───────────────┐
         │              │   Region B (Replica)  │
         │              │  ┌────────────────┐  │
         │              │  │ PostgreSQL     │  │
         │              │  │ (Read Replica) │  │
         │              │  └────────────────┘  │
         │              │  ┌────────────────┐  │
         │              │  │ ReBAC API      │  │
         │              │  │ (Read-only)    │  │
         │              │  └────────────────┘  │
         │              └──────────────────────┘

#10.2 监控指标

Python
REBAC_METRICS = {
    "rebac_check_total": Counter("总检查次数"),
    "rebac_check_latency": Histogram("检查延迟", buckets=[1, 5, 10, 50, 100, 500]),
    "rebac_check_allowed": Counter("允许次数"),
    "rebac_check_denied": Counter("拒绝次数"),
    "rebac_traversal_depth": Histogram("遍历深度", buckets=[1, 2, 3, 5, 8, 15]),
    "rebac_cache_hit_rate": Gauge("缓存命中率"),
    "rebac_tuple_count": Gauge("元组总数"),
    "rebac_expand_latency": Histogram("展开延迟"),
    "rebac_batch_size": Histogram("批量检查大小"),
}

#Key Takeaways

  1. ReBAC 将权限建模为对象关系图,天然契合本体论驱动的平台架构
  2. 三种关系推导方式(直接、计算、元组到用户集)覆盖所有权限继承场景
  3. Zanzibar 风格的 Zookie 机制解决分布式环境下的一致性问题
  4. 并行评估 + 短路优化 + 分层缓存确保 Check API 的低延迟
  5. 与 RBAC/ABAC 的三层流水线提供从粗到细的完整授权体系
  6. LookupResources 反向查询支持"用户能看到什么"的高效回答
  7. Expand API 和权限解释器为审计和调试提供完整的可观测性

#Next Article

下一篇 S6-05 策略即查询重写 将深入探讨如何将权限策略转化为数据库查询条件,实现行级和列级的数据访问控制,而无需在应用层逐行过滤。

#rebac #zanzibar #relationship-based-access-control #authorization #graph-traversal #coomia-dip #platform-engineering