PHP-Casbin

PHP-Casbin

基础知识

概述

Casbin是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。

Casbin是什么?

Casbin可以做到:

  1. 支持自定义请求的格式,默认的请求格式为{subject, object, action}
  2. 具有访问控制模型model和策略policy两个核心概念。
  3. 支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。
  4. 支持超级用户,如 rootAdministrator,超级用户可以不受授权策略的约束访问任意资源。
  5. 支持多种内置的操作符,如 keyMatch,方便对路径式的资源进行管理,如 /foo/bar 可以映射到 /foo*

Casbin不能做到:

  1. 身份认证 authentication(即验证用户的用户名、密码),casbin只负责访问控制。应该有其他专门的组件负责身份认证,然后由casbin进行访问控制,二者是相互配合的关系。
  2. 管理用户列表或角色列表。 Casbin 认为由项目自身来管理用户、角色列表更为合适, 用户通常有他们的密码,但是 Casbin 的设计思想并不是把它作为一个存储密码的容器。 而是存储RBAC方案中用户和角色之间的映射关系。

安装

composer require casbin/casbin

开始使用

require_once './vendor/autoload.php';

use Casbin\Enforcer;

$e = new Enforcer("path/to/model.conf", "path/to/policy.csv");

你可以在初始化Enforcer实例时,使用数据库代替文件,相关说明,后面将会提到

进行访问控制

$sub = "alice"; // 角色名
$obj = "data1"; // 访问的资源
$act = "read"; // 访问的权限

// 验证该角色是否有访问某资源的权限
if ($e->enforce($sub, $obj, $act) === true) {
    // 允许访问
} else {
    // 禁止访问
}

Casbin也提供了API用于权限管理,例如:你可以获取分配给某个角色的所有权限

$roles = $e->getRolesForUser("alice");

查看 Management APIRBAC API 获取更多的用法

请参考测试用例来了解更多的用法

工作原理

在 Casbin 中, 访问控制模型被抽象为基于 PERM (Policy, Effect, Request, Matcher) 的一个文件。 因此,切换或升级项目的授权机制与修改配置一样简单。 您可以通过组合可用的模型来定制您自己的访问控制模型。 例如,您可以在一个model中获得RBAC角色和ABAC属性,并共享一组policy规则。

Policy:策略 Effect:作用范围 Request:请求 Matcher:匹配器

Casbin中最基本、最简单的model是ACL。ACL中的model CONF为:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

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

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

ACL model的示例policy如下:

p, alice, data1, read
p, bob, data2, write

这表示:

  • alice可以读取data1
  • bob可以编写data2

对于过长的单行配置,您也可以通过在结尾处添加“\”进行断行:

# 匹配器
[matchers]
m = r.sub == p.sub && r.obj == p.obj \ 
  && r.act == p.act

此外,对于 ABAC,您在可以在 Casbin golang 版本中尝试下面的 (jCasbin 和 Node-Casbin 尚不支持)操作:

# 匹配器
[matchers]
m = r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')

但是你应确保数组的长度大于 1,否则的话将会导致 panic 。

对于更多操作,你可以查看govaluate

Model

支持的Models

  1. ACL (Access Control List, 访问控制列表)
  2. 具有的超级用户ACL
  3. 没有用户的 ACL: 对于没有身份验证或用户登录的系统尤其有用。
  4. 没有资源的 ACL: 某些场景可能只针对资源的类型, 而不是单个资源, 诸如 write-article, read-log等权限。 它不控制对特定文章或日志的访问。
  5. RBAC (基于角色的访问控制)
  6. 支持资源角色的RBAC: 用户和资源可以同时具有角色 (或组)。
  7. 支持域/租户的RBAC: 用户可以为不同的域/租户设置不同的角色集。
  8. ABAC (基于属性的访问控制): 支持利用resource.Owner这种语法糖获取元素的属性。
  9. RESTful: 支持路径, 如 /res/*, /res/: id 和 HTTP 方法, 如 GET, POST, PUT, DELETE。
  10. 拒绝优先: 支持允许和拒绝授权, 拒绝优先于允许。
  11. 优先级: 策略规则按照先后次序确定优先级,类似于防火墙规则。

例子

访问控制模型 Model 文件 Policy 文件
ACL basic_model.conf basic_policy.csv
具有超级用户的ACL basic_model_with_root.conf basic_policy.csv
没有用户的ACL basic_model_without_users.conf basic_policy_without_users.csv
没有资源的ACL basic_model_without_resources.conf basic_policy_without_resources.csv
RBAC rbac_model.conf rbac_policy.csv
支持资源角色的RBAC rbac_model_with_resource_roles.conf rbac_policy_with_resource_roles.csv
支持域/租户的RBAC rbac_model_with_domains.conf rbac_policy_with_domains.csv
ABAC abac_model.conf
RESTful keymatch_model.conf keymatch_policy.csv
拒绝优先 rbac_model_with_deny.conf rbac_policy_with_deny.csv
优先级 priority_model.conf priority_policy.csv

Model语法

  • Model CONF 至少应包含四个部分: [request_definition], [policy_definition], [policy_effect], [matchers]
  • 如果 model 使用 RBAC, 还需要添加[role_definition]部分。
  • 一个Model CONF可以包含注释。注释以#开头

request_definition:请求定义 policy_definition:策略定义 policy_effect:策略作用范围 matchers:匹配器 role_definition:角色定义

Request定义

[request_definition] 部分用于request的定义,它明确了 $e->enforce($sub, $obj, $act) 函数中参数的含义。

# 请求定义
[request_definition]
r = sub, obj, act

sub, obj, act 表示经典三元组: 访问实体 (Subject),访问资源 (Object) 和访问方法 (Action)。 但是, 你可以自定义你自己的请求表单, 如果不需要指定特定资源,则可以这样定义 sub、act ,或者如果有两个访问实体, 则为 sub、sub2、obj、act

Policy定义

[policy_definition] 部分是对policy的定义,以下文的 model 配置为例:

# 策略定义
[policy_definition]
p = sub, obj, act
p2 = sub, act

这些是我们对policy规则的具体描述

p, alice, data1, read
p2, bob, write-all-objects

policy部分的每一行称之为一个策略规则, 每条策略规则通常以形如p, p2policy type开头。 如果存在多个policy定义,那么我们会根据前文提到的policy type与具体的某条定义匹配。 上面的policy的绑定关系将会在matcher中使用, 罗列如下:

(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)

注1: 当前只支持形如 p的单个policy定义, 形如p2 类型的尚未支持。 通常情况下, 用户无需使用多个 policy 定义, 如果您有其他情形的policy定义诉求,请在 https://github.com/casbin/casbin/issues/new 提出issue告知我们。

注2: policy定义中的元素始终被视为字符串(string)对待, 如果您对此有疑问,请移步https://github.com/casbin/casbin/issues/113

Policy effect定义

[policy_effect] 部分是对policy生效范围的定义, 原语定义了当多个policy rule同时匹配访问请求request时,该如何对多个决策结果进行集成以实现统一决策。 以下示例展示了一个只有一条规则生效,其余都被拒绝的情况:

# 策略生效范围
[policy_effect]
e = some(where (p.eft == allow))

该Effect原语表示如果存在任意一个决策结果为allow的匹配规则,则最终决策结果为allow,即allow-override。 其中p.eft 表示策略规则的决策结果,可以为allow 或者deny,当不指定规则的决策结果时,取默认值allow 。 通常情况下,policy的p.eft默认为allow, 因此前面例子中都使用了这个默认值。

这是另一个policy effect的例子:

# 策略生效范围
[policy_effect]
e = !some(where (p.eft == deny))

该Effect原语表示不存在任何决策结果为deny的匹配规则,则最终决策结果为allow ,即deny-override。 some 量词判断是否存在一条策略规则满足匹配器。 any 量词则判断是否所有的策略规则都满足匹配器 (此处未使用)。

policy effect还可以利用逻辑运算符进行连接:

# 策略生效范围
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))

该Effect原语表示当至少存在一个决策结果为allow的匹配规则,且不存在决策结果为deny的匹配规则时,则最终决策结果为allow。 这时allow授权和deny授权同时存在,但是deny优先。

Matchers

[matchers] 原语定义了策略规则如何与访问请求进行匹配的匹配器,其本质上是布尔表达式,可以理解为Request、Policy等原语定义了关于策略和请求的变量,然后将这些变量代入Matcher原语中求值,从而进行策略决策。

# 匹配器
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

这是一个简单的例子,该Matcher原语表示,访问请求request中的subject、object、action三元组应与策略规则policy rule中的subject、object、action三元组分别对应相同。

Matcher原语支持+、 -、 *、 /等算数运算符,==,、!=、 >、 <等关系运算符以及&& (与)、|| (或)、 ! (非)等逻辑运算符。

: 虽然可以像其他原语一样的编写多个类似于 m1, m2 的matcher, 但是当前我们只支持一个有效的 matcher m。 通常情况下,您可以在一个matcher中使用上文提到的逻辑运算符来实现复杂的逻辑判断, 因而我们认为目前不需要支持多个matcher。 如果您对此有疑问,请告知我们(https://github.com/casbin/casbin/issues)。

matcher中的函数

matcher的强大与灵活之处在于您甚至可以在matcher中定义函数,这些函数可以是内置函数或自定义的函数。当前支持的内置函数如下:

函数 释义 示例
keyMatch(arg1, arg2) 参数 arg1 是一个 URL 路径,例如 /alice_data/resource1,参数 arg2 可以是URL路径或者是一个 * 模式,例如 /alice_data/*。此函数返回 arg1是否与 arg2 匹配。 keymatch_model.conf/keymatch_policy.csv
keyMatch2(arg1, arg2) 参数 arg1 是一个 URL 路径,例如 /alice_data/resource1,参数 arg2 可以是 URL 路径或者是一个 : 模式,例如/alice_data/:resource。此函数返回 arg1 是否与 arg2 匹配。 keymatch2_model.conf/keymatch2_policy.csv
regexMatch(arg1, arg2) arg1 可以是任何字符串。arg2 是一个正则表达式。它返回 arg1 是否匹配 arg2。 keymatch_model.conf/keymatch_policy.csv
ipMatch(arg1, arg2) arg1 是一个 IP 地址, 如 192.168.2.123。arg2 可以是 IP 地址或 CIDR, 如 192.168.2. 0/24。它返回 arg1 是否匹配 arg2。 ipmatch_model.conf/ipmatch_policy.csv

如何添加自定义函数(GO语言示例)

首先准备好一个有几个参数和一个布尔值返回值的函数:

func KeyMatch(key1 string, key2 string) bool {
    i := strings.Index(key2, "*")
    if i == -1 {
        return key1 == key2
    }

    if len(key1) > i {
        return key1[:i] == key2[:i]
    }
    return key1 == key2[:i]
}

然后用 interface{} 类型包装此函数:

func KeyMatchFunc(args ...interface{}) (interface{}, error) {
    name1 := args[0].(string)
    name2 := args[1].(string)

    return (bool)(KeyMatch(name1, name2)), nil
}

最后, 将该函数注册到 Casbin enforcer:

e.AddFunction("my_func", KeyMatchFunc)

现在, 您可以像下面这样使用 model 中的函数:

# 匹配器
[matchers]
m = r.sub == p.sub && my_func(r.obj, p.obj) && r.act == p.act

RBAC

角色定义

[role_definition] 是RBAC角色继承关系的定义。 Casbin 支持 RBAC 系统的多个实例, 例如, 用户可以具有角色及其继承关系, 资源也可以具有角色及其继承关系。 这两个 RBAC 系统不会互相干扰。

此部分是可选的。 如果在模型中不使用 RBAC 角色, 则省略此部分。

# 角色定义
[role_definition]
g = _, _
g2 = _, _

上述角色定义表明, g 是一个 RBAC系统, g2 是另一个 RBAC 系统。 _, _表示角色继承关系的前项和后项,即前项继承后项角色的权限。 一般来讲,如果您需要进行角色和用户的绑定,直接使用g 即可。 当您需要表示角色(或者组)与用户和资源的绑定关系时,可以使用gg2 这样的表现形式。 请参见 rbac_modelrbac_model_with_resource_roles 的示例。

在Casbin里,我们以policy表示中实际的用户角色映射关系 (或是资源-角色映射关系),例如:

p, data2_admin, data2, read
g, alice, data2_admin

这意味着 alice 是角色 data2_admin的一个成员。 alice 在这里可以是用户、资源或角色。 Cabin 只是将其识别为一个字符串。

接下来在matcher中,应该像下面的例子一样检查角色信息:

# 匹配器
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

它表示请求中的sub应该在策略中定义了sub角色。

有几个注意事项:

  1. Casbin 只存储用户角色的映射关系。
  2. Cabin 没有验证用户是否是有效的用户,或者角色是一个有效的角色。 这应该通过认证来解决。
  3. RBAC 系统中的用户名称和角色名称不应相同。因为Casbin将用户名和角色识别为字符串, 所以当前语境下Casbin无法得出这个字面量到底指代用户 alice 还是角色 alice。 这时,使用明确的 role_alice ,问题便可迎刃而解。
  4. 假设A具有角色 BB 具有角色 C,并且 A 有角色 C。 这种传递性在当前版本会造成死循环。

角色层次

Casbin 的 RBAC 支持 RBAC1 的角色层次结构功能,如果 alice具有role1, role1具有role2,则 alice 也将拥有 role2 并继承其权限。

下面是一个称为层次结构级别的概念。 因此, 此示例的层次结构级别为2。 对于Casbin中的内置角色管理器, 可以指定最大层次结构级别。 默认值为10。 这意味着终端用户 alice 只能继承10个级别的角色。

// GO语言示例
// NewRoleManager重写了默认角色管理RoleManger的构造方法
func NewRoleManager(maxHierarchyLevel int) rbac.RoleManager {
    rm := RoleManager{}
    rm.allRoles = &sync.Map{}
    rm.maxHierarchyLevel = maxHierarchyLevel
    rm.hasPattern = false

    return &rm
}

如何区分用户和角色?

在RBAC中,Casbin不对用户和角色进行区分。 它们都被视为字符串。 如果你只使用单层的RBAC模型(角色不会成为另一个角色的成员)。 可以使用 e.GetAllSubjects() 获取所有用户,e.GetAllRoles() 获取所有角色。 它们会为规则 g, u, r 分别列出所有的 ur

但在使用多层RBAC模型时(带有角色继承),你的应用不会记录一个名字(字符串)是用户还是角色,或者用户和角色有相同的名字。 可以给角色加上像 role::admin 的前缀再传递到Casbin中。 由此可以通过查看前缀来区分用户和角色。

如何查询隐性角色或权限?

当用户通过RBAC层次结构继承角色或权限,而不是直接在策略规则中分配它们时,我们将这种类型的分配称为 implicit。 要查询这种隐式关系,需要使用以下两个api: GetImplicitRolesForUser()以及 GetImplicitPermissionsForUser 替代GetRolesForUser() 以及 GetPermissionsForUser. 有关详情,请参阅 this GitHub issue

在 RBAC 中使用模式匹配

有时,您希望将具有特定模式的某些subjects(或objects)自动授予某个角色。 RBAC中的模式匹配函数可以帮助做到这一点。 模式匹配函数与前一个函数共享相同的参数和返回值:matcher function

我们知道RBAC在matcher中通常表示为g(r.sub, p.sub)。 然后我们将使用以下策略:

p, alice, book_group, read
g, /book/1, book_group
g, /book/2, book_group

所以 alice可以读所有的book,包括 book 1book 2。 但是可能有成千上万个book,使用 g 策略规则将每一个book添加到book角色(或组)是非常单调乏味的。

但是使用模式匹配函数,您可以只用一行代码编写策略:

g, /book/:id, book_group

Casbin会自动将 /book/1/book/2匹配成模式/book/:id。 您只需要向强制程序注册该功能,如:

// GO语言示例
e.rm.(*defaultrolemanager.RoleManager).AddMatchingFunc("KeyMatch2", util.KeyMatch2)

您可以在这里看到完整的示例 :here

值得注意的是:

  1. 只有g种的第一参数 (aka 用户) 支持模式函数。 您正在使用第三个参数(domain),目前不支持。

角色管理器

角色管理器用于管理Casbin中的RBAC角色层次结构(用户角色映射)。 角色管理器可以从Casbin策略规则或外部源(如LDAP、Okta、Auth0、Azure AD等)检索角色数据。 我们支持角色管理器的不同实现。 为了保持代码轻量级,我们没有把角色管理器代码放在主库中(默认的角色管理器除外)。 下面提供了Casbin角色管理器的完整列表。 欢迎任何第三方对角色manager进行新的贡献,如果有请告知我们,我们将把它放在这个列表中:

角色管理器 作者 描述
Default Role Manager (built-in) Casbin 支持存储在Casbin策略中的角色层次结构
Session Role Manager EDOMO Systems 支持存储在Casbin策略中的角色层次结构,以及基于时间范围的会话
Okta Role Manager Casbin 支持存储在Okta中的角色层次结构
Auth0 Role Manager Casbin 支持存储在 Auth0's Authorization Extension 授权扩展名中的角色层次结构

对于开发人员:所有角色manager必须实现 RoleManager接口。 Session Role Manager可以用作参考实现。

RBAC with Domains

域租户的角色定义

在Casbin中的RBAC角色可以是全局或是基于特定于域的。 特定域的角色意味着当用户处于不同的域/租户群体时,用户所表现的角色也不尽相同。 这对于像云服务这样的大型系统非常有用,因为用户通常分属于不同的租户群体。

域/租户的角色定义应该类似于:

# 角色定义
[role_definition]
g = _, _, _

第三个 _ 表示域/租户的名称, 此部分不应更改。

然后,策略可以是:

p, admin, tenant1, data1, read
p, admin, tenant2, data2, read

g, alice, admin, tenant1
g, alice, user, tenant2

该实例表示tenant1的域内角色admin 可以读取data1alicetenant1域中具有admin角色,但在tenant2域中具有user角色, 所以alice可以有读取data1的权限。 同理,因为alice不是tenant2admin,所以她访问不了data2

接下来在matcher中,应该像下面的例子一样检查角色信息:

# 匹配器
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act

更多示例参见: rbac_with_domains_model.conf

ABAC

什么是ABAC模式?

ABAC是 基于属性的访问控制,可以使用主体、客体或动作的属性,而不是字符串本身来控制访问。 您之前可能就已经听过 XACML ,是一个复杂的 ABAC 访问控制语言。 与XACML相比,Casbin的ABAC非常简单: 在ABAC中,可以使用struct(或基于编程语言的类实例) 而不是字符串来表示模型元素。

例如,ABAC的官方实例如下:

# 请求定义
[request_definition]
r = sub, obj, act

# 政策定义
[policy_definition]
p = sub, obj, act

# 政策应用范围
[policy_effect]
e = some(where (p.eft == allow))

# 匹配器
[matchers]
m = r.sub == r.obj.Owner

我们使用 r.obj.所有者 代替 r.obj matcher。 在 Enforce() 函数中传递的 r.obj 函数是结构或类实例,而不是字符串。 Casbin将使用映像来检索 obj结构或类中的成员变量。

这里是 r.obj construction 或 class 的定义:

// GO语言示例
type testResource struct {
    Name  string
    Owner string
}

如何使用ABAC?

简单地说,要使用ABAC,您需要做两件事:

  1. 在模型匹配器中指定属性。
  2. 将元素的结构或类实例作为Casbin的Enforce() 的参数传入。

Note:

  1. 目前,仅请求元素,例如 r.such、[ r.obj,] r.action 等等支持ABAC的元素。 您不能在策略元素上使用它,比如p.sub,因为在Casbin的策略中没有定义结构或者类。
  2. 您可以在一个matcher中使用多个ABAC属性,例如:m = r.sub.Domain == r.obj.Domain

存储

Model存储

与 policy 不同,model 只能加载,不能保存。 因为我们认为 model 不是动态组件,不应该在运行时进行修改,所以我们没有实现一个 API 来将 model 保存到存储中。

但是,好消息是,我们提供了三种等效的方法来静态或动态地加载模型:

从 .CONF 文件中加载 model

当你向 Casbin 团队寻求帮助时,他们会给你这个 Casbin 最常用的方法,此方法对于初学者来说很容易理解并且便于分享。

.CONF文件的内容 examples/rbac_model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

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

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

接着你可以加载模型文件如下:

// GO语言示例
e := casbin.NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

从代码加载 model

型可以从代码中动态初始化,不需要使用 .CONF。下面是RBAC模型的一个例子:

// GO语言示例
// 初始化model
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("g", "g", "_, _")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")

// 使用自己的 adapter 替换。
a := persist.NewFileAdapter("examples/rbac_policy.csv")

// 创建一个 enforcer。
e := casbin.NewEnforcer(m, a)

从字符串加载的 model

或者您可以从多行字符串加载整个模型文本。这种方法的优点是您不需要维护模型文件。

// GO语言示例
// Initialize the model from a string.
text :=
`
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

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

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m := NewModel(text)

// Load the policy rules from the .CSV file adapter.
// Replace it with your adapter to avoid files.
a := persist.NewFileAdapter("examples/rbac_policy.csv")

// Create the enforcer.
e := casbin.NewEnforcer(m, a)

Policy存储

在Casbin中,策略存储作为adapter实现。请参照:</docs/en/adapters>。

Policy Subset Loading

一些adapter支持过滤策略管理。 这意味着Casbin加载的策略是基于给定过滤器的存储策略的子集。 当解析整个策略成为性能瓶颈时,这将会允许在大型多租户环境中有效地执行策略。

要使用支持的adapter处理过滤后的策略,只需调用 LoadFilteredPolicy 方法。 过滤器参数的有效格式取决于所用的适配器。 为了防止意外数据丢失,当策略已经加载, SavePolicy 方法会被禁用。

例如,下面的代码片段使用内置的过滤文件adapter和带有域的RBAC模型。 在本例中,过滤器将策略限制为单个域。 除 "domain1" 以外的任何域策略行被忽略:

// GO语言示例
import "github.com/casbin/casbin"

enforcer := casbin.NewEnforcer()

adapter := fileadapter.NewFilteredAdapter("examples/rbac_with_domains_policy.csv")
enforcer.InitWithAdapter("examples/rbac_with_domains_model.conf", adapter)

filter := &fileadapter.Filter{
    P: []string{"", "domain1"},
    G: []string{"", "", "domain1"},
}
enforcer.LoadFilteredPolicy(filter)

// The loaded policy now only contains the entries pertaining to "domain1".

Extensions

Adapters

在Casbin中,策略存储作为adapter(Casbin的中间件) 实现。 Casbin用户可以使用adapte从存储中加载策略规则 (aka LoadPolicy()) 或者将策略规则保存到其中 (aka SavePolicy())。 为了保持代码轻量级,我们没有把adapte代码放在主库中。

目前支持的adapter列表

Casbin角色管理器的完整列表如下所示。 欢迎任何第三方对adapter进行新的贡献,如果有请通知我们,我们将把它放在这个列表中:)

适配器 类型 作者 自动保存 描述
File Adapter (built-in) File Casbin For .CSV (Comma-Separated Values) files
Database Adapter ORM Casbin MySQL, PostgreSQL, SQLite, Microsoft SQL Server are supported by techone/database
Zend Db Adapter ORM Casbin MySQL, PostgreSQL, SQLite, Oracle, IBM DB2, Microsoft SQL Server, Other PDO Driver are supported by zend-db
Doctrine DBAL Adapter(Recommend) ORM Casbin Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.
Medoo Adapter ORM Casbin Medoo is a lightweight PHP Database Framework to Accelerate Development, supports all SQL databases, including MySQL, MSSQL, SQLite, MariaDB, PostgreSQL, Sybase, Oracle and more.

这里有一些你需要知道的事情:

  1. 如果使用显式或隐式adapter调用casbin.NewEnforcer(),策略将自动加载。
  2. 可以调用e.LoadPolicy() 来从存储中重新加载策略规则。
  3. 如果adapter不支持Auto-Save特性,则在添加或删除策略时不能将策略规则自动保存回存储器。 您可以手动调用SavePolicy() 来保存所有策略规则。

例子

这里我门提供了几个例子:

文件适配器 (内置)

下面展示了如何对内置的文件适配器进行初始化:

use Casbin\Enforcer;

$e = new Enforcer('examples/basic_model.conf', 'examples/basic_policy.csv');

同样的:

use Casbin\Enforcer;
use Casbin\Persist\Adapters\FileAdapter;

$a = new FileAdapter('examples/basic_policy.csv');
$e = new Enforcer('examples/basic_model.conf', $a);

MySQL 适配器

下面展示了如何初始化一个MySQL适配器

// https://github.com/php-casbin/dbal-adapter

use Casbin\Enforcer;
use CasbinAdapter\DBAL\Adapter as DatabaseAdapter;

$config = [
    // driver的可选值:
    // pdo_mysql,pdo_sqlite,pdo_pgsql,pdo_oci (unstable),pdo_sqlsrv,pdo_sqlsrv,
    // mysqli,sqlanywhere,sqlsrv,ibm_db2 (unstable),drizzle_pdo_mysql
    'driver'     => 'pdo_mysql', 
    'host' => '127.0.0.1',
    'dbname' => 'test',
    'user' => 'root',
    'password' => '',
    'port' => '3306',
];

$a = DatabaseAdapter::newAdapter($config);
$e = new Enforcer('examples/basic_model.conf', $a);

使用自建的adapter

你可以使用自定义的适配器,例如:

// GO语言示例
import (
    "github.com/casbin/casbin"
    "github.com/your-username/your-repo"
)

a := yourpackage.NewAdapter(params)
e := casbin.NewEnforcer("examples/basic_model.conf", a)

在运行时进行加载或保存配置信息

你也许希望重新加载模型,重新加载策略或者在初始化后保存策略

// GO语言示例
// 从模型配置文件中重新加载模型
e.LoadModel()

// 从文件或数据库中重新加载策略
e.LoadPolicy()

// Save the current policy (usually after changed with Casbin API) back to file/database.
// 将当前策略保存到文件或数据库(通常在使用Casbin API之后)
e.SavePolicy()

自动保存

有一个称为适配器自动保存的功能。当一个适配器支持自动保存功能时,就意味着它能够支持从存储器中添加一条策略或删除一条策略。这与savePolicy()不同,后者将删除存储中的所有策略规则,并将所有策略规则从casbin Enforcer保存到存储中。因此,当策略规则的数量很大时,它可能会遇到性能问题。

当适配器支持自动保存时,可以通过enforcer.enableautosave()函数切换此选项。默认情况下,该选项处于启用状态(如果适配器支持它)。

需要注意的是:

  1. Auto-Save 特性是可选的。 Adapter可以选择是否实现它。
  2. Auto-Save 只在Casbin enforcer使用的adapter支持它时才有效。
  3. 查看上述adapter列表中的 AutoSave列,查看adapter是否支持 Auto-Save

这里有一个关于自动保存的例子:

// GO语言示例
import (
    "github.com/casbin/casbin"
    "github.com/casbin/xorm-adapter"
    _ "github.com/go-sql-driver/mysql"
)

// By default, the AutoSave option is enabled for an enforcer.
a := xormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/")
e := casbin.NewEnforcer("examples/basic_model.conf", a)

// Disable the AutoSave option.
e.EnableAutoSave(false)

// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,
// it doesn't affect the policy in the storage.
e.AddPolicy(...)
e.RemovePolicy(...)

// Enable the AutoSave option.
e.EnableAutoSave(true)

// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,
// but also affects the policy in the storage.
e.AddPolicy(...)
e.RemovePolicy(...)

更多示例,请参考:https://github.com/casbin/xorm-adapter/blob/master/adapter_test.go

如何编写 Adapter

所有适配器需要至少实现两个适配器接口中的两个方法:LoadPolicy(model model.Model) error and SavePolicy(model model.Model) error

其他三个功能是可选的。如果适配器支持自动保存功能,则应该实现它们。

方法 类型 描述
LoadPolicy() 强制的 从存储中加载所有策略规则
SavePolicy() 强制的 将所有策略规则保存到存储中
AddPolicy() 可选择的 向存储中添加策略规则
RemovePolicy() 可选择的 从存储中删除策略规则
RemoveFilteredPolicy() 可选择的 从存储中删除匹配筛选器的策略规则

注意:如果适配器不支持自动保存功能,它应该为三个可选功能提供一个空实现。如果您不提供它,编译器将会报错。下面是一个例子:

// GO语言示例
// AddPolicy adds a policy rule to the storage.
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
    return errors.New("not implemented")
}

// RemovePolicy removes a policy rule from the storage.
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
    return errors.New("not implemented")
}

// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
    return errors.New("not implemented")
}

casbin执行器在调用这三个可选函数时将忽略未实现错误。

关于数据库表结构的创建

作为约定,适配器应该能够自动创建一个名为casbin的数据库(如果它不存在),并将其用于策略存储。请使用xorm适配器作为参考实现:https://github.com/casbin/xorm-adapter

Watchers

​ 我们支持使用像etcd这样的分布式消息传递系统来保持多个casbin执行器实例之间的一致性。因此,我们的用户可以同时使用多个Casbin enforcers来处理大量的权限检查请求。

与策略存储 adapters类似,我们没有把watcher的代码放在主库中。 任何对新消息系统的支持都应该作为atcher程序来实现。 完整的Casbin watchers列表如下所示。 欢迎任何第三方对 watcher 进行新的贡献,如果有请告知我们,我将把它放在这个列表中:

Watcher Type Author Description
Etcd Watcher KV store Casbin Watcher for etcd
NATS Watcher Messaging system Soluto Watcher for NATS
ZooKeeper Watcher KV store Grepsr Watcher for Apache ZooKeeper
Redis Watcher KV store @billcobbler Watcher for Redis
GCP Pub/Sub Watcher Messaging system LivingPackets Watcher for Google Cloud Platform PUB/SUB

Role Managers

角色管理器用于管理Casbin中的RBAC角色层次结构(用户角色映射)。 角色管理器可以从Casbin策略规则或外部源(如LDAP、Okta、Auth0、Azure AD等) 检索角色数据。 我们支持角色管理器的不同实现。 为了保持代码轻量级,我们没有把角色管理器代码放在主库中(默认的角色管理器除外)。 下面提供了Casbin角色管理器的完整列表。 欢迎任何第三方对角色管理器进行新的贡献,如果有请告知我们,我将把它放在这个列表中:

Role manager Author Description
Default Role Manager (built-in) Casbin 支持存储在Casbin策略中的角色层次结构

对于开发人员:所有角色管理器都必须实现RoleManager接口。默认角色管理器可以用作引用实现。

中间件

WEB框架

Name Description
Laravel The PHP framework for web artisans, via plugin: laravel-casbin
Laravel An authorization library for the laravel framework, via plugin: Laravel Authorization
Yii PHP Framework A fast, secure, and efficient PHP framework, via plugin: yii-casbin
CakePHP Build fast, grow solid PHP Framework, via plugin: cake-casbin
CodeIgniter4 Associate users with roles and permissions in CodeIgniter4 Web Framework, via plugin: CodeIgniter Permission
ThinkPHP 5.1 The ThinkPHP 5.1 framework, via plugin: think-casbin
ThinkPHP 6.0 The ThinkPHP 6.0 framework, via plugin: think-authz
Symfony The Symfony PHP framework, via plugin: symfony-casbin

API

管理 API

提供对Casbin策略管理完全支持的基本API。

参考

全局变量 e是执行者实例。

$e = new Enforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');

获取当前策略中显示的主题列表:

$allSubjects = $e->getAllSubjects();

获取当前命名策略中显示的主题列表:

$allNamedSubjects = $e->getAllNamedSubjects("p");

获取当前策略中显示的对象列表:

$allObjects = $e->getAllObjects();

获取当前命名策略中显示的对象列表:

$allNamedObjects = $e->getAllNamedObjects("p");

获取当前策略中显示的操作列表:

$allActions = $e->getAllActions();

获取当前命名策略中显示的操作列表:

$allNamedActions = $e->getAllNamedActions("p");

获取当前策略中显示的角色列表

$allRoles = $e->getAllRoles();

获取当前命名策略中显示的角色列表:

$allNamedRoles = $e->getAllNamedRoles('g');

获取策略中的所有授权规则:

$policy = $e->getPolicy();

获取策略中的所有授权规则,可以指定字段筛选器:

$filteredPolicy = $e->getFilteredPolicy(0, "alice");

获取命名策略中的所有授权规则:

$namedPolicy = $e->getNamedPolicy("p");

获取命名策略中的所有授权规则,可以指定字段过滤器:

$filteredNamedPolicy = $e->getFilteredNamedPolicy("p", 0, "bob");

获取策略中的所有角色继承规则:

$groupingPolicy = $e->getGroupingPolicy();

获取策略中的所有角色继承规则,可以指定字段筛选器:

$filteredGroupingPolicy = $e->getFilteredGroupingPolicy(0, "alice");

获取策略中的所有角色继承规则:

$namedGroupingPolicy = $e->getNamedGroupingPolicy("g");

获取策略中的所有角色继承规则

$namedGroupingPolicy = $e->getFilteredNamedGroupingPolicy("g", 0, "alice");

确定是否存在授权规则

$hasPolicy = $e->hasPolicy('data2_admin', 'data2', 'read');

确定是否存在命名授权规则

$hasNamedPolicy = $e->hasNamedPolicy("p", "data2_admin", "data2", "read");

向当前策略添加授权规则。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true

$added = $e->addPolicy('eve', 'data3', 'read');

向当前命名策略添加授权规则。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true:

$added = $e->addNamedPolicy("p", "eve", "data3", "read");

从当前策略中删除授权规则

$removed = $e->removePolicy("alice", "data1", "read");

移除当前策略中的授权规则,可以指定字段筛选器。 RemovePolicy 从当前策略中删除授权规则:

$removed = $e->removeFilteredPolicy(0, "alice", "data1", "read");

从当前命名策略中删除授权规则:

$removed = $e->removeNamedPolicy("p", "alice", "data1", "read");

从当前命名策略中移除授权规则,可以指定字段筛选器:

$removed = $e->removeFilteredNamedPolicy("p", 0, "alice", "data1", "read");

确定是否存在角色继承规则

$has = $e->hasGroupingPolicy("alice", "data2_admin");

确定是否存在命名角色继承规则:

$has = $e->hasNamedGroupingPolicy("g", "alice", "data2_admin");

向当前策略添加角色继承规则。 如果规则已经存在,函数返回false,并且不会添加规则。 如果规则已经存在,函数返回false,并且不会添加规则:

$added = $e->addGroupingPolicy("group1", "data2_admin");

将命名角色继承规则添加到当前策略。 如果规则已经存在,函数返回false,并且不会添加规则。 否则,函数通过添加新规则并返回true:

$added = $e->addNamedGroupingPolicy("g", "group1", "data2_admin");

从当前策略中删除角色继承规则:

$removed = $e->removeGroupingPolicy("alice", "data2_admin");

从当前策略中移除角色继承规则,可以指定字段筛选器:

$removed = $e->removeFilteredGroupingPolicy(0, "alice");

从当前命名策略中移除角色继承规则:

$removed = $e->removeNamedGroupingPolicy("g", "alice");

从当前命名策略中移除角色继承规则,可以指定字段筛选器:

$removed = $e->removeFilteredNamedGroupingPolicy("g", 0, "alice");

添加自定义函数:

func customFunction($key1, $key2) {
    if ($key1 == "/alice_data2/myid/using/res_id" && $key2 == "/alice_data/:resource") {
        return true;
    } elseif ($key1 == "/alice_data2/myid/using/res_id" && $key2 == "/alice_data2/:id/using/:resId") {
        return true;
    } else {
        return false;
    }
}

func customFunctionWrapper(...$args){
    $key1 := $args[0];
    $key2 := $args[1];

    return customFunction($key1, $key2);
}

$e->addFunction("keyMatchCustom", customFunctionWrapper);

RBAC API

一个更友好的RBAC API。 这个API是Management API的子集。 RBAC用户可以使用这个API来简化代码。

参考

全局变量 e是实施者实例。

$e = new Enforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');

获取用户具有的角色:

$res = $e->getRolesForUser("alice");

获取具有角色的用户:

$res = $e->getUsersForRole("data1_admin");

确定用户是否具有角色:

$res = $e->hasRoleForUser("alice", "data1_admin");

为用户添加角色。 如果用户已经拥有该角色(aka不受影响),则返回false:

$e->addRoleForUser("alice", "data2_admin");

删除用户的角色。 如果用户没有该角色(aka不受影响),则返回false:

$e->deleteRoleForUser("alice", "data1_admin");

删除用户的所有角色。 如果用户没有任何角色(aka不受影响),则返回false:

$e->deleteRolesForUser("alice");

删除一个用户。 如果用户不存在,则返回false(也就是说不受影响):

$e->deleteUser("alice");

删除一个角色:

$e->deleteRole("data2_admin");

删除权限。 如果权限不存在,则返回false(aka不受影响):

$e->deletePermission("read");

为用户或角色添加权限。 如果用户或角色已经拥有该权限(aka不受影响),则返回false:

$e->addPermissionForUser("bob", "read");

删除用户或角色的权限。 如果用户或角色没有权限(aka不受影响),则返回false:

$e->deletePermissionForUser("bob", "read");

删除用户或角色的权限。 如果用户或角色没有任何权限(aka不受影响),则返回false:

$e->deletePermissionsForUser("bob");

获取用户或角色的权限:

$e->getPermissionsForUser("bob");

确定用户是否具有权限:

$e->hasPermissionForUser("alice", []string{"read"});

获取用户具有的隐式角色。 与GetRolesForUser() 相比,该函数除了直接角色外还检索间接角色:

例如:

g, alice, role:admin
g, role:admin, role:user

GetRolesForUser("alice") 只能获取到: ["role:admin"].
But GetImplicitRolesForUser("alice") 却能获取到: ["role:admin", "role:user"].

$e->getImplicitRolesForUser("alice");

获取用户或角色的隐式权限。与getPermissionsForuser()相比,此函数检索继承角色的权限

p, admin, data1, read
p, alice, data2, read
g, alice, admin

GetPermissionsForUser("alice") 只能获取到: [["alice", "data2", "read"]].
But GetImplicitPermissionsForUser("alice") 却能获取到: [["admin", "data1", "read"], ["alice", "data2", "read"]].

$e->getImplicitPermissionsForUser("alice");

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容