golang基于Zanzibar论文实现细粒度授权系统插件库openfga的使用

Golang基于Zanzibar论文实现细粒度授权系统插件库OpenFGA的使用

OpenFGA是一个高性能且灵活的授权/权限引擎,专为开发者设计,灵感来源于Google Zanzibar论文。它使开发者能够轻松建模应用程序权限并将细粒度授权集成到应用程序中。

OpenFGA简介

OpenFGA支持内存数据存储用于快速开发,以及可插拔的数据库模块。目前支持PostgreSQL 14+、MySQL 8和SQLite(目前处于测试阶段)。它提供了HTTP API和gRPC API,并有多种语言的SDK,包括Java、Node.js/JavaScript、GoLang、Python和.NET

快速开始

使用Docker安装

docker pull openfga/openfga
docker run -p 8080:8080 -p 3000:3000 openfga/openfga run

使用Docker Compose安装

  1. 下载docker-compose.yaml文件:
curl -LO https://openfga.dev/docker-compose.yaml
  1. 运行:
docker compose up

使用Go SDK

以下是一个完整的Golang示例,展示如何使用OpenFGA Go SDK:

package main

import (
	"context"
	"fmt"
	"log"

	openfga "github.com/openfga/go-sdk"
	"github.com/openfga/go-sdk/client"
)

func main() {
	// 初始化OpenFGA客户端
	configuration, err := client.NewConfiguration(client.Configuration{
		ApiScheme: "http",              // 可选,"https"
		ApiHost:   "localhost:8080",    // 必需
		StoreId:   "",                  // 不是必需的,可以在单个请求中设置
	})
	if err != nil {
		log.Fatal(err)
	}

	fgaClient := client.NewSdkClient(&configuration)

	// 创建存储
	storeName := "openfga-demo"
	createStoreResponse, err := fgaClient.CreateStore(context.Background()).Body(client.ClientCreateStoreRequest{
		Name: storeName,
	}).Execute()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Created store with ID: %s\n", createStoreResponse.Id)

	// 定义授权模型
	typeDefs := []openfga.TypeDefinition{
		{
			Type: "user",
			Relations: &map[string]openfga.Userset{
				"member": {},
			},
		},
		{
			Type: "document",
			Relations: &map[string]openfga.Userset{
				"reader": {
					This: &map[string]interface{}{},
				},
				"writer": {
					This: &map[string]interface{}{},
				},
				"owner": {
					This: &map[string]interface{}{},
				},
			},
			Metadata: &openfga.Metadata{
				Relations: &map[string]openfga.RelationMetadata{
					"reader": {
						DirectlyRelatedUserTypes: &[]openfga.RelationReference{
							{Type: "user"},
						},
					},
					"writer": {
						DirectlyRelatedUserTypes: &[]openfga.RelationReference{
							{Type: "user"},
						},
					},
					"owner": {
						DirectlyRelatedUserTypes: &[]openfga.RelationReference{
							{Type: "user"},
						},
					},
				},
			},
		},
	}

	// 写入授权模型
	writeAuthModelResponse, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(client.ClientWriteAuthorizationModelRequest{
		TypeDefinitions: typeDefs,
	}).Execute()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Authorization Model ID: %s\n", writeAuthModelResponse.AuthorizationModelId)

	// 写入关系元组
	user := "user:bob"
	document := "document:planning"
	relation := "writer"

	_, err = fgaClient.Write(context.Background()).Body(client.ClientWriteRequest{
		Writes: []client.ClientTupleKey{
			{
				User:     user,
				Relation: relation,
				Object:   document,
			},
		},
	}).Options(client.ClientWriteOptions{
		AuthorizationModelId: openfga.PtrString(writeAuthModelResponse.AuthorizationModelId),
	}).Execute()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Successfully wrote tuple")

	// 检查权限
	checkResponse, err := fgaClient.Check(context.Background()).Body(client.ClientCheckRequest{
		User:     user,
		Relation: relation,
		Object:   document,
	}).Options(client.ClientCheckOptions{
		AuthorizationModelId: openfga.PtrString(writeAuthModelResponse.AuthorizationModelId),
	}).Execute()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Can %s %s %s? %v\n", user, relation, document, checkResponse.Allowed)
}

使用说明

验证安装

运行以下命令验证OpenFGA是否安装成功:

curl -X POST 'localhost:8080/stores' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "openfga-demo"
}'

如果一切正常,您将收到如下响应:

{
  "id": "01G3EMTKQRKJ93PFVDA1SJHWD2",
  "name": "openfga-demo",
  "created_at": "2022-05-19T17:11:12.888680Z",
  "updated_at": "2022-05-19T17:11:12.888680Z"
}

开发环境

OpenFGA提供了一个开发用的Playground,访问地址为:http://localhost:3000/playground

要禁用Playground,可以运行:

./openfga run --playground-enabled=false

生产环境准备

OpenFGA核心服务自2021年12月起已被Auth0 FGA用于生产环境。内存存储适配器仅用于开发目的,不建议在生产环境中使用。

更多资源


更多关于golang基于Zanzibar论文实现细粒度授权系统插件库openfga的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang基于Zanzibar论文实现细粒度授权系统插件库openfga的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


基于Zanzibar论文的OpenFGA细粒度授权系统使用指南

OpenFGA是一个基于Google Zanzibar论文理念的开源细粒度授权系统,它提供了一种灵活的方式来定义和管理复杂的访问控制关系。下面我将介绍如何在Golang中使用OpenFGA实现细粒度授权。

1. OpenFGA核心概念

OpenFGA基于以下几个核心概念:

  • 授权模型(Authorization Model):定义实体类型和它们之间的关系
  • 元组(Tuples):存储具体的关系实例
  • 检查(Check):验证用户是否有权限
  • 列表(List):列出用户有权限的所有资源

2. 安装OpenFGA Go SDK

首先安装OpenFGA的Go SDK:

go get github.com/openfga/go-sdk

3. 初始化OpenFGA客户端

package main

import (
	"context"
	"fmt"
	"log"

	openfga "github.com/openfga/go-sdk"
	"github.com/openfga/go-sdk/client"
)

func main() {
	// 配置OpenFGA客户端
	configuration, err := client.NewConfiguration(client.Configuration{
		ApiScheme: "http",
		ApiHost:   "localhost:8080", // OpenFGA服务器地址
	})
	if err != nil {
		log.Fatal(err)
	}

	fgaClient := client.NewOpenFgaClient(configuration)
	
	// 设置认证信息(如果有)
	// fgaClient.SetStoreId("your-store-id")
	// fgaClient.SetAuthorizationModelId("your-model-id")
}

4. 定义授权模型

func createAuthorizationModel(fgaClient *client.OpenFgaClient) {
	model := openfga.WriteAuthorizationModelRequest{
		TypeDefinitions: []openfga.TypeDefinition{
			{
				Type: "user",
			},
			{
				Type: "document",
				Relations: map[string]openfga.Userset{
					"reader": {
						This: &map[string]interface{}{},
					},
					"writer": {
						This: &map[string]interface{}{},
					},
					"owner": {
						This: &map[string]interface{}{},
					},
					"can_view": {
						Union: &openfga.Usersets{
							Child: []openfga.Userset{
								{This: &map[string]interface{}{}},
								{ComputedUserset: &openfga.ObjectRelation{
									Object:   openfga.PtrString(""),
									Relation: openfga.PtrString("reader"),
								}},
								{ComputedUserset: &openfga.ObjectRelation{
									Object:   openfga.PtrString(""),
									Relation: openfga.PtrString("writer"),
								}},
								{ComputedUserset: &openfga.ObjectRelation{
									Object:   openfga.PtrString(""),
									Relation: openfga.PtrString("owner"),
								}},
							},
						},
					},
					"can_edit": {
						Union: &openfga.Usersets{
							Child: []openfga.Userset{
								{This: &map[string]interface{}{}},
								{ComputedUserset: &openfga.ObjectRelation{
									Object:   openfga.PtrString(""),
									Relation: openfga.PtrString("writer"),
								}},
								{ComputedUserset: &openfga.ObjectRelation{
									Object:   openfga.PtrString(""),
									Relation: openfga.PtrString("owner"),
								}},
							},
						},
					},
				},
				Metadata: &openfga.Metadata{
					Relations: map[string]openfga.RelationMetadata{
						"reader": {
							DirectlyRelatedUserTypes: []openfga.RelationReference{
								{Type: "user"},
							},
						},
						"writer": {
							DirectlyRelatedUserTypes: []openfga.RelationReference{
								{Type: "user"},
							},
						},
						"owner": {
							DirectlyRelatedUserTypes: []openfga.RelationReference{
								{Type: "user"},
							},
						},
					},
				},
			},
		},
		SchemaVersion: "1.1",
	}

	response, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(model).Execute()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Authorization model created with ID: %s\n", *response.AuthorizationModelId)
}

5. 写入关系元组

func writeTuples(fgaClient *client.OpenFgaClient) {
	// 添加关系元组
	body := client.ClientWriteRequest{
		Writes: []client.ClientTupleKey{
			{
				User:     "user:alice",
				Relation: "owner",
				Object:   "document:budget",
			},
			{
				User:     "user:bob",
				Relation: "writer",
				Object:   "document:budget",
			},
			{
				User:     "user:charlie",
				Relation: "reader",
				Object:   "document:budget",
			},
		},
	}

	_, err := fgaClient.Write(context.Background()).Body(body).Execute()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Tuples written successfully")
}

6. 检查权限

func checkPermission(fgaClient *client.OpenFgaClient) {
	checkRequest := client.ClientCheckRequest{
		User:     "user:bob",
		Relation: "can_edit",
		Object:   "document:budget",
	}

	response, err := fgaClient.Check(context.Background()).Body(checkRequest).Execute()
	if err != nil {
		log.Fatal(err)
	}

	if *response.Allowed {
		fmt.Println("Bob can edit the budget document")
	} else {
		fmt.Println("Bob cannot edit the budget document")
	}
}

7. 列出用户有权限的资源

func listDocuments(fgaClient *client.OpenFgaClient) {
	listRequest := client.ClientListObjectsRequest{
		User:     "user:bob",
		Relation: "can_view",
		Type:     "document",
	}

	response, err := fgaClient.ListObjects(context.Background()).Body(listRequest).Execute()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Bob can view the following documents:")
	for _, obj := range response.Objects {
		fmt.Println(obj)
	}
}

8. 完整示例

package main

import (
	"context"
	"fmt"
	"log"

	openfga "github.com/openfga/go-sdk"
	"github.com/openfga/go-sdk/client"
)

func main() {
	// 初始化客户端
	configuration, err := client.NewConfiguration(client.Configuration{
		ApiScheme: "http",
		ApiHost:   "localhost:8080",
	})
	if err != nil {
		log.Fatal(err)
	}
	fgaClient := client.NewOpenFgaClient(configuration)

	// 创建授权模型
	createAuthorizationModel(fgaClient)

	// 写入关系元组
	writeTuples(fgaClient)

	// 检查权限
	checkPermission(fgaClient)

	// 列出可访问资源
	listDocuments(fgaClient)
}

// 前面定义的函数放在这里...

9. 实际应用建议

  1. 缓存策略:OpenFGA检查操作可能会频繁调用,考虑添加缓存层
  2. 批量操作:使用批量API减少网络开销
  3. 监控:监控授权检查的延迟和错误率
  4. 模型设计:精心设计授权模型,避免过于复杂的嵌套关系

OpenFGA提供了强大的细粒度授权能力,通过合理的设计可以实现复杂的访问控制场景。以上代码展示了基本用法,实际应用中可能需要根据具体需求进行调整。

回到顶部