Casbin 是一个强大的访问控制库,支持多种访问控制模型,如 ACL(访问控制列表)、RBAC(基于角色的访问控制)、ABAC(基于属性的访问控制)等。

安装 Casbin

go get github.com/casbin/casbin/v2

基本概念

在 Casbin 中,权限管理的核心模型由请求定义策略定义策略效果匹配器四部分组成。下面逐一解释这些概念。

请求定义(Request Definition)

请求定义描述了访问请求的结构。最常见的是 r = sub, obj, act,其中:

  • sub:表示请求发起的主体(通常是用户或服务)。

  • obj:表示请求要访问的对象(比如文件、API 等)。

  • act:表示主体对对象执行的操作(比如读取、写入、删除等)。

例如,r = sub, obj, act 表示一个请求由发起主体、操作对象和操作类型组成。

策略定义(Policy Definition)

策略定义描述了权限策略的结构。同样常见的结构是 p = sub, obj, act,其中:

  • sub:表示策略中的主体。

  • obj:表示策略中的对象。

  • act:表示策略中的操作类型。

策略定义与请求定义的结构保持一致,便于匹配。

策略效果(Policy Effect)

策略效果定义了如何合并多个匹配的策略规则来决定最终的访问结果。常见的策略效果有:

  • e = some(where (p.eft == allow)):只要有一个策略规则允许,则允许访问。

  • e = !some(where (p.eft == deny)):如果没有一个策略规则拒绝,则允许访问。

  • e = all(where (p.eft == allow)):所有的策略规则都允许,才允许访问。

在以下配置中:

  • e = some(where (p.eft == allow)) 表示只要有一个策略规则允许访问,就允许访问。

匹配器(Matchers)

匹配器定义了如何将请求与策略规则进行匹配。常见的匹配器有:

  • r.sub == p.sub && r.obj == p.obj && r.act == p.act:完全匹配。

  • r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act):部分匹配,其中 keyMatch 用于字符串前缀匹配,regexMatch 用于正则表达式匹配。

在以下配置中:

  • r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) || r.sub == "root" 表示主体与策略主体相匹配,且对象通过前缀匹配,操作通过正则表达式匹配,或者主体是 root 时允许访问。

配置文件详解

Casbin 使用配置文件来定义具体的访问控制模型。一个典型的配置文件如下:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) || r.sub == "root"
  • 请求定义r = sub, obj, act 表示请求由主体、对象和操作组成。

  • 策略定义p = sub, obj, act 表示策略由主体、对象和操作组成。

  • 策略效果e = some(where (p.eft == allow)) 表示只要有一个策略规则允许访问,就允许访问。

  • 匹配器r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) || r.sub == "root" 表示主体与策略主体相匹配,且对象通过前缀匹配,操作通过正则表达式匹配,或者主体是 root 时允许访问。

在 Gin 框架中使用 Casbin

下面是一个简单的例子,介绍如何在 Gin 框架中集成 Casbin 进行权限管理。

1. 安装依赖

go get github.com/casbin/casbin/v2

go get github.com/gin-gonic/gin

2. 初始化 Casbin

创建一个 casbin_model.conf 文件,内容如下:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) || r.sub == "root"

创建一个 casbin_policy.csv 文件,内容如下:

p, alice, /api/data, GET
p, bob, /api/data, POST
p, admin, /api/*, (GET)|(POST)|(DELETE)

3. 编写 Gin 代码

使用csv文件存储权限配置。

package main

import (
	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
	fileadapter "github.com/casbin/casbin/v2/persist/file-adapter"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

func main() {
	// 加载模型文件
	m, err := model.NewModelFromFile("casbin_model.conf")
	if err != nil {
		log.Printf("Failed to load casbin model: %v", err)
		return
	}
	// 检查模型是否有效
	if m == nil {
		log.Println("Loaded casbin model is nil")
		return
	}
	//创建文件适配器
	adapter := fileadapter.NewAdapter("casbin_policy.csv")
	// 创建 Casbin 执行器,传入模型和适配器
	e, err := casbin.NewEnforcer(m, adapter)
	if err != nil {
		log.Printf("Failed to create casbin enforcer: %v", err)
		return
	}
	// 从适配器加载策略
	err = e.LoadPolicy()
	if err != nil {
		log.Printf("Failed to load policy: %v", err)
		return
	}
	// 创建 Gin 引擎
	r := gin.Default()

	// 权限验证中间件
	authMiddleware := func(c *gin.Context) {
		sub := c.GetHeader("X-User") // 从请求头中获取用户信息
		obj := c.Request.URL.Path    // 获取请求路径
		act := c.Request.Method      // 获取请求方法

		// 正确接收 Enforce 方法的两个返回值
		allowed, err := e.Enforce(sub, obj, act)
		if err != nil {
			// 处理错误,返回 500 状态码
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Authorization error"})
			c.Abort()
			return
		}

		if !allowed {
			c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
			c.Abort()
		}
	}

	// 定义路由
	r.GET("/api/data", authMiddleware, func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"data": "Here is your data"})
	})

	r.POST("/api/data", authMiddleware, func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "Data created successfully"})
	})

	r.POST("/user/data", authMiddleware, func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "user created successfully"})
	})

	// 启动服务
	r.Run(":8080")
}

4. 使用 GORM 适配器初始化 Casbin

上述示例,创建一个 casbin_policy.csv 文件的方式编辑权限不方便使用。一般会把权限配置在数据库中方便管理。

GORM 是一个功能强大的 Go 语言 ORM(对象关系映射)库,可以简化数据库操作,使开发者能够更专注于业务逻辑。GORM Casbin 适配器允许你将 Casbin 的策略存储在数据库中,通过 GORM 进行操作,从而实现更复杂的权限管理。
下面介绍 GORM 的基本使用方法及其在 Casbin 中的适配器。

GORM适配器是什么?

GORM适配器是Casbin与GORM(Go语言的ORM库)之间的桥梁,允许你将Casbin的策略存储在支持GORM的数据库中,比如MySQL、PostgreSQL、SQLite等,而不是默认的CSV文件。GORM(Go Object Relational Mapping)是一个用于Go程序的ORM库,它使得数据库操作更加简便和类型安全。

GORM适配器做什么用?

GORM适配器的主要用途是将Casbin的策略存储在数据库中,从而提供更持久化、可扩展的权限管理解决方案。使用GORM适配器可以带来以下好处:

  1. 持久化存储:策略可以存储在数据库中,即使应用重启,策略依然存在。

  2. 可扩展性:使用数据库可以存储大量的策略规则,处理复杂的权限逻辑。

  3. 灵活性:可以通过数据库查询、更新策略规则,而不是手动编辑CSV文件。

  4. 事务支持:在更新策略时,可以利用数据库的事务机制,确保数据的一致性和完整性。

如何在Casbin中使用GORM适配器?

首先,你需要安装GORM适配器。你可以使用以下命令来安装:

go get github.com/casbin/gorm-adapter/v2

接下来,你需要修改之前的代码,以支持GORM适配器。假设你使用的是MySQL数据库,以下是具体步骤:

1. 安装GORM和MySQL驱动

go get -u gorm.io/gorm

go get -u gorm.io/driver/mysql

2. 修改初始化Casbin的部分
package main

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "github.com/casbin/casbin/v2"
    "github.com/casbin/gorm-adapter/v2"
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    // 连接数据库
    dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    // 初始化GORM适配器
    a, err := gormadapter.NewAdapter(db) // db is your GORM database
    if err != nil {
        panic(err)
    }

    // 加载模型和策略文件
    m, _ := model.NewModelFromFile("casbin_model.conf")
    e, _ := casbin.NewEnforcer(m, a)
    // 添加策略
    e.AddPolicy("alice", "/api/data", "GET")
    e.AddPolicy("bob", "/api/data", "POST")
    e.AddPolicy("admin", "/api/*", "(GET)|(POST)|(DELETE)")
   // 自动迁移模式
    a.CreateTables()

    // 或者从 CSV 文件加载策略
    // e.LoadPolicyFile("casbin_policy.csv")

    // 创建 Gin 引擎
    r := gin.Default()

    // 权限验证中间件
    authMiddleware := func(c *gin.Context) {
        sub := c.GetHeader("X-User")   // 从请求头中获取用户信息
        obj := c.Request.URL.Path     // 获取请求路径
        act := c.Request.Method       // 获取请求方法

        if !e.Enforce(sub, obj, act) {
            c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"})
            c.Abort()
        }
    }

    // 定义路由
    r.GET("/api/data", authMiddleware, func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"data": "Here is your data"})
    })

    r.POST("/api/data", authMiddleware, func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "Data created successfully"})
    })

    // 启动服务
    r.Run(":8080")
}

5. 测试接口

  • 使用 curl 或 Postman 等工具发送请求。

  • 通过设置不同的请求头中的 X-User 值来测试不同的用户权限。

curl -H "X-User: alice" http://localhost:8080/api/data
curl -H "X-User: bob" -X POST http://localhost:8080/api/data
curl -H "X-User: admin" -X DELETE http://localhost:8080/api/data

6. 处理错误

如果请求未通过权限验证,Casbin 将返回 403 Forbidden 错误。

7. 注意事项

  • 配置文件路径:确保 casbin_model.confcasbin_policy.csv 文件路径正确。

  • 请求头:本例中通过请求头 X-User 来传递用户信息,实际应用中可以根据需要调整。

  • 策略文件格式:策略文件中的策略规则需要符合模型定义的结构。