第十四章:OpenFGA — 细粒度授权引擎

“OpenFGA 让你像 Google 一样管理权限 — 基于关系,而非角色。”

        mindmap
  root((OpenFGA))
    核心概念
      Type
      Relation
      Tuple
    API
      Check
      ListObjects
      Expand
      Write
    授权模型
      DSL
      Google Drive
      GitHub
      多租户
    部署
      Server
      PostgreSQL
      SDK
    

14.1 OpenFGA 是什么

OpenFGA(Fine-Grained Authorization)是由 Auth0/Okta 开源的细粒度授权引擎,基于 Google Zanzibar 论文实现。它是 CNCF Sandbox 项目。

OpenFGA 架构:
┌──────────┐    ┌──────────────────┐    ┌──────────┐
│  应用     │───▶│   OpenFGA Server │───▶│ 数据存储  │
│          │    │                  │    │PostgreSQL│
│ Check()  │    │  ┌────────────┐  │    │  MySQL   │
│ Write()  │    │  │ 授权模型    │  │    │          │
│ List()   │    │  │ 关系元组    │  │    │          │
│          │    │  │ 图遍历引擎  │  │    │          │
└──────────┘    │  └────────────┘  │    └──────────┘
                └──────────────────┘

14.2 核心概念

授权模型(Authorization Model)

# OpenFGA DSL 语法
model
  schema 1.1

type user

type organization
  relations
    define admin: [user]
    define member: [user] or admin

type document
  relations
    define owner: [user]
    define editor: [user, organization#member]
    define viewer: [user, organization#member] or editor or owner
    define can_edit: editor or owner
    define can_view: viewer
    define can_delete: owner

关系元组(Relationship Tuple)

# 格式:object#relation@subject

# Alice 是 budget 文档的 owner
document:budget#owner@user:alice

# Bob 是 budget 文档的 editor
document:budget#editor@user:bob

# Acme 组织的所有 member 都是 roadmap 文档的 viewer
document:roadmap#viewer@organization:acme#member

# Carol 是 Acme 组织的 member
organization:acme#member@user:carol
→ 因此 Carol 可以查看 roadmap 文档(通过关系链推导)

API 操作

API

功能

示例

Check

检查是否有权限

Carol 能查看 roadmap 吗?→ ✅

ListObjects

列出有权限的对象

Carol 能查看哪些文档?

ListUsers

列出有权限的用户

谁能编辑 budget?

Expand

展开权限关系树

budget 的 viewer 包含哪些人?

Write

写入关系元组

添加/删除权限关系

Read

读取关系元组

查询已有的权限关系

14.3 实战场景:Google Drive 权限模型

model
  schema 1.1

type user

type group
  relations
    define member: [user, group#member]

type folder
  relations
    define owner: [user]
    define editor: [user, group#member] or owner
    define viewer: [user, group#member] or editor
    define parent: [folder]
    # 继承父文件夹的权限
    define can_edit: editor or owner or can_edit from parent
    define can_view: viewer or can_edit or can_view from parent

type document
  relations
    define owner: [user]
    define editor: [user, group#member] or owner
    define viewer: [user, group#member] or editor
    define parent: [folder]
    define can_edit: editor or owner or can_edit from parent
    define can_view: viewer or can_edit or can_view from parent
    define can_share: owner
    define can_delete: owner
关系元组示例:
# 文件夹层次
folder:engineering#parent@folder:root
folder:backend#parent@folder:engineering

# 文档归属
document:api-spec#parent@folder:backend

# 权限分配
folder:root#viewer@group:all-employees#member
folder:engineering#editor@group:eng-team#member
document:api-spec#owner@user:alice

# 权限推导:
# Bob 是 eng-team 的 member
# → Bob 是 engineering 文件夹的 editor
# → Bob 是 backend 文件夹的 editor(继承)
# → Bob 可以编辑 api-spec 文档(继承)

14.4 Python SDK 完整示例

import asyncio
from openfga_sdk import (
    ClientConfiguration,
    OpenFgaClient,
    ClientWriteRequest,
    ClientTupleKey,
    ClientCheckRequest,
    ClientListObjectsRequest,
)
from openfga_sdk.client.models import WriteAuthorizationModelRequest

async def main():
    # 1. 配置客户端
    config = ClientConfiguration(
        api_url="http://localhost:8080",
        store_id="your-store-id",
    )

    async with OpenFgaClient(config) as client:
        # 2. 创建授权模型
        model = WriteAuthorizationModelRequest(
            schema_version="1.1",
            type_definitions=[
                {"type": "user"},
                {
                    "type": "document",
                    "relations": {
                        "owner": {"this": {}},
                        "editor": {"this": {}},
                        "viewer": {
                            "union": {
                                "child": [
                                    {"this": {}},
                                    {"computedUserset": {"relation": "editor"}},
                                    {"computedUserset": {"relation": "owner"}},
                                ]
                            }
                        },
                    },
                    "metadata": {
                        "relations": {
                            "owner": {"directly_related_user_types": [{"type": "user"}]},
                            "editor": {"directly_related_user_types": [{"type": "user"}]},
                            "viewer": {"directly_related_user_types": [{"type": "user"}]},
                        }
                    },
                },
            ],
        )
        auth_model = await client.write_authorization_model(model)
        print(f"模型 ID: {auth_model.authorization_model_id}")

        # 3. 写入关系元组
        await client.write(ClientWriteRequest(
            writes=[
                ClientTupleKey(
                    user="user:alice",
                    relation="owner",
                    object="document:budget",
                ),
                ClientTupleKey(
                    user="user:bob",
                    relation="editor",
                    object="document:budget",
                ),
                ClientTupleKey(
                    user="user:carol",
                    relation="viewer",
                    object="document:budget",
                ),
            ]
        ))
        print("关系元组写入成功")

        # 4. 检查权限
        result = await client.check(ClientCheckRequest(
            user="user:bob",
            relation="viewer",
            object="document:budget",
        ))
        print(f"Bob 能查看 budget? {result.allowed}")  # True(editor 隐含 viewer)

        result = await client.check(ClientCheckRequest(
            user="user:carol",
            relation="editor",
            object="document:budget",
        ))
        print(f"Carol 能编辑 budget? {result.allowed}")  # False

        # 5. 列出用户可访问的文档
        objects = await client.list_objects(ClientListObjectsRequest(
            user="user:alice",
            relation="viewer",
            type="document",
        ))
        print(f"Alice 可查看的文档: {objects.objects}")

asyncio.run(main())

14.5 OpenFGA 部署

# docker-compose.yml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: openfga
      POSTGRES_PASSWORD: openfga_password
      POSTGRES_DB: openfga
    volumes:
      - pgdata:/var/lib/postgresql/data

  openfga-migrate:
    image: openfga/openfga:latest
    command: migrate
    environment:
      OPENFGA_DATASTORE_ENGINE: postgres
      OPENFGA_DATASTORE_URI: postgres://openfga:openfga_password@postgres:5432/openfga?sslmode=disable
    depends_on:
      - postgres

  openfga:
    image: openfga/openfga:latest
    command: run
    environment:
      OPENFGA_DATASTORE_ENGINE: postgres
      OPENFGA_DATASTORE_URI: postgres://openfga:openfga_password@postgres:5432/openfga?sslmode=disable
    ports:
      - "8080:8080"   # HTTP API
      - "8081:8081"   # gRPC API
      - "3000:3000"   # Playground
    depends_on:
      openfga-migrate:
        condition: service_completed_successfully

volumes:
  pgdata:

14.6 OpenFGA vs RBAC

维度

RBAC

OpenFGA (ReBAC)

授权依据

角色

关系

粒度

角色级

对象级

“谁能访问X”

需要遍历所有角色

直接查询

“X能访问什么”

需要遍历所有资源

ListObjects API

继承

角色层次

关系链(更灵活)

性能

O(1) 角色检查

图遍历(有缓存)

适用场景

简单权限

协作应用、SaaS

14.7 小结

  • OpenFGA 基于 Google Zanzibar,提供关系驱动的细粒度授权

  • 核心是关系元组object#relation@subject

  • 支持权限继承(通过关系链推导)

  • 提供 Check、ListObjects、ListUsers 等丰富的 API

  • 适合协作应用(文档、项目管理)和多租户 SaaS

  • 与 RBAC 相比,OpenFGA 在资源级别的细粒度控制上更有优势