Syntax for Models
模型配置(CONF)应至少有四个部分:
[request_definition]
,[policy_definition]
,[policy_effect]
和[matchers]
。如果模型使用基于角色的访问控制(RBAC),它还应包括
[role_definition]
部分。模型配置(CONF)可以包含注释。 注释以
#
符号开始,#
符号后的所有内容都将被注释掉。
请求定义
[request_definition]
部分定义了e.Enforce(...)
函数中的参数。
[request_definition]
r = sub, obj, act
在这个例子中,sub
,obj
和act
代表了经典的访问三元组:主体(访问实体),对象(被访问资源)和动作(访问方法)。 然而,你可以自定义你自己的请求格式。 例如,如果你不需要指定特定的资源,你可以使用sub, act
,或者如果你有两个访问实体,你可以使用sub, sub2, obj, act
。
策略定义
[policy_definition]
是策略的定义。 它定义了策略的含义。 例如,我们有以下模型:
[policy_definition]
p = sub, obj, act
p2 = sub, act
我们有以下策略(如果在策略文件中):
p, alice, data1, read
p2, bob, write-all-objects
策略中的每一行都被称为策略规则。 每个策略规则都以策略类型
开始,如p
或p2
。 如果有多个定义,它用于匹配策略定义。 上述策略显示了以下绑定。 绑定可以在匹配器中使用。
(alice, data1, read) -> (p.sub, p.obj, p.act)
(bob, write-all-objects) -> (p2.sub, p2.act)
策略规则中的元素始终被视为字符串
。 如果你对此有任何疑问,请参考以下讨论:https://github.com/casbin/casbin/issues/113
策略效果
[policy_effect]
是策略效果的定义。 如果多个策略规则匹配请求,它决定是否应批准访问请求。 例如,一条规则允许,另一条规则拒绝。
[policy_effect]
e = some(where (p.eft == allow))
上述策略效果意味着,如果有任何匹配的allow
策略规则,最终效果是allow
(也称为允许覆盖)。 p.eft
是策略的效果,它可以是allow
或deny
。 这是可选的,其默认值是allow
。 由于我们在上面没有指定它,所以它使用默认值。
策略效果的另一个例子是:
[policy_effect]
e = !some(where (p.eft == deny))
这意味着,如果没有匹配的deny
策略规则,最终的效果是allow
(也称为deny-override)。 some
意味着存在一个匹配的策略规则。 any
意味着所有匹配的策略规则(这里未使用)。 策略效果甚至可以与逻辑表达式连接:
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
这意味着必须至少有一个匹配的allow
策略规则,并且不能有任何匹配的deny
策略规则。 因此,以这种方式,支持允许和拒绝授权,并且拒绝优先。
尽管我们设计了如上的策略效果语法,但当前的实现只使用硬编码的策略效果。 这是因为我们发现没有那么大的需要那种级别的灵活性。 所以现在,你必须使用其中一个内置的策略效果,而不是定制你自己的。
支持的内置策略效果有:
策略效果 | 含义 | 例子 |
---|---|---|
some(where (p.eft == allow)) | allow-override | ACL, RBAC, etc. |
!some(where (p.eft == deny)) | deny-override | Deny-override |
some(where (p.eft == allow)) && !some(where (p.eft == deny)) | allow-and-deny | Allow-and-deny |
priority(p.eft) || deny | priority | Priority |
subjectPriority(p.eft) | 基于角色的优先级 | 主题-优先级 |
匹配器
[匹配器]是策略匹配器的定义。 匹配器是定义如何根据请求评估策略规则的表达式。
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
上述匹配器是最简单的,意味着请求中的主题、对象和动作应与策略规则中的相匹配。
匹配器中可以使用算术运算符如+, -, *, /
和逻辑运算符如&&, ||, !
。
匹配器中表达式的顺序
表达式的顺序可以极大地影响性能。 看看下面的例子以获取更多详细信息:
const rbac_models = `
[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
`
func TestManyRoles(t *testing.T) {
m, _ := model.NewModelFromString(rbac_models)
e, _ := NewEnforcer(m, false)
roles := []string{"admin", "manager", "developer", "tester"}
// 2500 projects
for nbPrj := 1; nbPrj < 2500; nbPrj++ {
// 4 objects and 1 role per object (so 4 roles)
for _, role := range roles {
roleDB := fmt.Sprintf("%s_project:%d", role, nbPrj)
objectDB := fmt.Sprintf("/projects/%d", nbPrj)
e.AddPolicy(roleDB, objectDB, "GET")
}
jasmineRole := fmt.Sprintf("%s_project:%d", roles[1], nbPrj)
e.AddGroupingPolicy("jasmine", jasmineRole)
}
e.AddGroupingPolicy("abu", "manager_project:1")
e.AddGroupingPolicy("abu", "manager_project:2499")
// With same number of policies
// User 'abu' has only two roles
// User 'jasmine' has many roles (1 role per policy, here 2500 roles)
request := func(subject, object, action string) {
t0 := time.Now()
resp, _ := e.Enforce(subject, object, action)
tElapse := time.Since(t0)
t.Logf("RESPONSE %-10s %s\t %s : %5v IN: %+v", subject, object, action, resp, tElapse)
if tElapse > time.Millisecond*100 {
t.Errorf("More than 100 milliseconds for %s %s %s : %+v", subject, object, action, tElapse)
}
}
request("abu", "/projects/1", "GET") // really fast because only 2 roles in all policies and at the beginning of the casbin_rule table
request("abu", "/projects/2499", "GET") // fast because only 2 roles in all policies
request("jasmine", "/projects/1", "GET") // really fast at the beginning of the casbin_rule table
request("jasmine", "/projects/2499", "GET") // slow and fails the only 1st time <<<< pb here
request("jasmine", "/projects/2499", "GET") // fast maybe due to internal cache mechanism
// same issue with non-existing roles
// request("jasmine", "/projects/999999", "GET") // slow fails the only 1st time <<<< pb here
// request("jasmine", "/projects/999999", "GET") // fast maybe due to internal cache mechanism
}
执行时间可能会很长,长达6秒。
go test -run ^TestManyRoles$ github.com/casbin/casbin/v2 -v
=== RUN TestManyRoles
rbac_api_test.go:598: RESPONSE abu /projects/1 GET : true IN: 438.379µs
rbac_api_test.go:598: RESPONSE abu /projects/2499 GET : true IN: 39.005173ms
rbac_api_test.go:598: RESPONSE jasmine /projects/1 GET : true IN: 1.774319ms
rbac_api_test.go:598: RESPONSE jasmine /projects/2499 GET : true IN: 6.164071648s
rbac_api_test.go:600: More than 100 milliseconds for jasmine /projects/2499 GET : 6.164071648s
rbac_api_test.go:598: RESPONSE jasmine /projects/2499 GET : true IN: 12.164122ms
--- FAIL: TestManyRoles (6.24s)
FAIL
FAIL github.com/casbin/casbin/v2 6.244s
FAIL
然而,如果我们调整匹配器中表达式的顺序,并将更耗时的表达式如函数放在后面,执行时间将会非常短。
将上述示例中匹配器中的表达式顺序更改为:
[matchers]
m = r.obj == p.obj && g(r.sub, p.sub) && r.act == p.act
go test -run ^TestManyRoles$ github.com/casbin/casbin/v2 -v
=== RUN TestManyRoles
rbac_api_test.go:599: RESPONSE abu /projects/1 GET : true IN: 786.635µs
rbac_api_test.go:599: RESPONSE abu /projects/2499 GET : true IN: 4.933064ms
rbac_api_test.go:599: RESPONSE jasmine /projects/1 GET : true IN: 2.908534ms
rbac_api_test.go:599: RESPONSE jasmine /projects/2499 GET : true IN: 7.292963ms
rbac_api_test.go:599: RESPONSE jasmine /projects/2499 GET : true IN: 6.168307ms
--- PASS: TestManyRoles (0.05s)
PASS
ok github.com/casbin/casbin/v2 0.053s
多个部分类型
如果你需要多个策略定义或多个匹配器,你可以使用p2
或m2
作为例子。 实际上,上述的四个部分都可以使用多种类型,语法是r
后面跟一个数字,如r2
或e2
。 默认情况下,这四个部分应一一对应。 例如,你的r2
部分只会使用m2
匹配器来匹配p2
策略。
你可以将EnforceContext
作为enforce
方法的第一个参数来指定类型。 EnforceContext
的定义如下:
- Go
- Node.js
- Java
EnforceContext{"r2","p2","e2","m2"}
type EnforceContext struct {
RType string
PType string
EType string
MType string
}
const enforceContext = new EnforceContext('r2', 'p2', 'e2', 'm2');
class EnforceContext {
constructor(rType, pType, eType, mType) {
this.pType = pType;
this.eType = eType;
this.mType = mType;
this.rType = rType;
}
}
EnforceContext enforceContext = new EnforceContext("2");
public class EnforceContext {
private String pType;
private String eType;
private String mType;
private String rType;
public EnforceContext(String suffix) {
this.pType = "p" + suffix;
this.eType = "e" + suffix;
this.mType = "m" + suffix;
this.rType = "r" + suffix;
}
}
这是一个使用示例。 请参考model和policy。 请求如下:
- Go
- Node.js
- Java
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
enforceContext := NewEnforceContext("2")
// You can also specify a certain type individually
enforceContext.EType = "e"
// Don't pass in EnforceContext; the default is r, p, e, m
e.Enforce("alice", "data2", "read") // true
// Pass in EnforceContext
e.Enforce(enforceContext, struct{ Age int }{Age: 70}, "/data1", "read") //false
e.Enforce(enforceContext, struct{ Age int }{Age: 30}, "/data1", "read") //true
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
const enforceContext = new NewEnforceContext('2');
// You can also specify a certain type individually
enforceContext.eType = "e"
// Don't pass in EnforceContext; the default is r, p, e, m
e.Enforce("alice", "data2", "read") // true
// Pass in EnforceContext
e.Enforce(enforceContext, {Age: 70}, "/data1", "read") //false
e.Enforce(enforceContext, {Age: 30}, "/data1", "read") //true
// Pass in a suffix as a parameter to NewEnforceContext, such as 2 or 3, and it will create r2, p2, etc.
EnforceContext enforceContext = new EnforceContext("2");
// You can also specify a certain type individually
enforceContext.seteType("e");
// Don't pass in EnforceContext; the default is r, p, e, m
e.enforce("alice", "data2", "read"); // true
// Pass in EnforceContext
// TestEvalRule is located in https://github.com/casbin/jcasbin/blob/master/src/test/java/org/casbin/jcasbin/main/AbacAPIUnitTest.java#L56
e.enforce(enforceContext, new AbacAPIUnitTest.TestEvalRule("alice", 70), "/data1", "read"); // false
e.enforce(enforceContext, new AbacAPIUnitTest.TestEvalRule("alice", 30), "/data1", "read"); // true
特殊语法
你也可以使用“in”运算符,这是唯一一个带有文本名称的运算符。 此运算符检查右侧的数组,看是否包含等于左侧值的值。 等式是通过使用==运算符确定的,这个库不检查值之间的类型。 只要两个值可以被转换为接口{}并且仍然可以用==检查等式,它们就会按预期工作。 注意,你可以为数组使用一个参数,但它必须是一个[]接口{}。
也可以参考rbac_model_matcher_using_in_op,keyget2_model和keyget_model。
示例:
[request_definition]
r = sub, obj
...
[matchers]
m = r.sub.Name in (r.obj.Admins)
e.Enforce(Sub{Name: "alice"}, Obj{Name: "a book", Admins: []interface{}{"alice", "bob"}})
表达式求值器
Casbin中的匹配器评估是由每种语言的表达式求值器实现的。 Casbin整合了他们的力量,提供了统一的PERM语言。 除了这里提供的模型语法外,这些表达式求值器可能提供额外的功能,这些功能可能不被其他语言或实现支持。 在使用这个功能时,请谨慎。
每个Casbin实现使用的表达式求值器如下:
实现 | 语言 | 表达式求值器 |
---|---|---|
Casbin | Golang | https://github.com/Knetic/govaluate |
jCasbin | Java | https://github.com/killme2008/aviator |
Node-Casbin | Node.js | https://github.com/donmccurdy/expression-eval |
PHP-Casbin | PHP | https://github.com/symfony/expression-language |
PyCasbin | Python | https://github.com/danthedeckie/simpleeval |
Casbin.NET | C# | https://github.com/davideicardi/DynamicExpresso |
Casbin4D | Delphi | https://github.com/casbin4d/Casbin4D/tree/master/SourceCode/Common/Third%20Party/TExpressionParser |
casbin-rs | Rust | https://github.com/jonathandturner/rhai |
casbin-cpp | C++ | https://github.com/ArashPartow/exprtk |
如果您遇到Casbin的性能问题,可能是由于表达式评估器的低效率引起的。 您可以直接向Casbin或表达式评估器提出问题,寻求提高性能的建议。 有关更多详细信息,请参阅Benchmarks部分。