request_definition 请求定义
请求定义,r = {sub, obj, act}
或者是r = {sub, dom, obj, act}
sub
请求主体,譬如是admin
这个用户来请求。dom
在多商户情况下使用,代表不同的域。譬如agent
代理。obj
访问对象,譬如/api/getUser
这个链接。act
访问方法(action 动作)
,譬如GET, POST, PUT, DELETE
。
[request_definition]
r = sub, obj, act
policy_definition 策略定义
策略定义对应的是请求定义的内容,p = {sub, obj, act, eft}
或者p = {sub, dom, obj, act, eft} 最后的
eft可以省略,默认情况下的
eft是
allow。各个解释的含义和
request` 中的一样。
[policy_definition]
r = sub, obj, act
; 我们可以定义策略内容如下
admin, /api/login, post
admin, /api/getUser, get
admin, /api/read/:id, get
admin, /api/updateUser, post
super, /api/login, get
role_definition 角色定义
一般我们rbac
的时候才使用角色,定义如g = _, _
,第一个_
, 代表请求实体 (可以看做请求用户,譬如admin
),第二个_
角色群组,对应的是policy
中的sub
,实现了用户到角色的转换。在matchers
中,我们可以使用g(r.sub, p.sub)
。如果我们在
macthers
中使用的是g(p.sub, r.sub)
,则在policy
中g, 角色, 用户
[role_definition]
g = _, _
; 如果 在 policy.csv 中把 group 定义为
g, admin, super
; 当请求用户是 `admin` 时,就同时能匹配 `admin` 和 `super` 角色两个主体的策略规则。
带域 (domain
) 的角色
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.obj == p.obj && && g(r.sub, p.sub, r.dom) && r.act == p.act && r.dom == p.dom
我们定义 policy.csv
文件内容如下
p, agent, agent.user, /api/users, get
p, test_agent, agent.user, /api/update, post
p, admin, admin.user, /api/users, get
p, user, user.user, /api/users, get
g, test_user, admin, admin.user
g, test_user, agent, agent.user
g, test_user, user, user.user
g, test_agent, agent, agent.user
这里要注意的是g = _, _, _
中的第三个_
代表domain
域/租户,所以在policy
中定义时domain
要放在最后边,同上边我们解释过的,因为在matchers
中定义的是g(r.sub, p.pub, r.dom)
。
; g(r.sub, p.sub, r.dom) 在 policy 定义如下
g, 用户, 角色, 域
; g(p.sub, r.sub, r.dom)
g, 角色, 用户, 域
; g(r.dom, r.sub, p.sub)
g, 域, 用户, 角色
; 也就是 policy 定义的 `g` 是和 `matchers` 中定义的位置是对应的。
这时候,当我们请求用户test_user
, 浏览器GET
方式访问url path
是/api/users
,访问的域是agent.user
。test_user
会通过g(r.sub, p.sub)
获取到admin、agent、user
的角色,再通过g(r.sub, p.sub, r.dom)
中的agent.user
匹配到agent, agent.user, /api/users, get
。
effect_policy 策略效果
effect 中的定义都是固定的,可以 casbin官网 中看到定义方式。
[effect_policy]
; 匹配结果是 eft 是 allow 就通过
e = some(where(p.eft == allow))
; 当 eft 是 allow,并且 policy 中不存在 deny 的定义
; p, admin, /api/user, allow
; p, admin, /api/user, deny
; 这时候当 admin 访问 /api/user 因为存在 deny 所以就返回 false
e = some(where(p.eft == allow)) && !some(where(p.eft == deny))
; 优先级,如果有 allow 先匹配 allow, 这时候在通过上边的访问就通过了
e = priority(e.eft) || deny
matchers 匹配器
通过对request
、policy
和role
匹配,来返回匹配到的policy
条目中的effect
再通过effect policy
来返回结果。
[matchers]
; 先匹配访问主体,再匹配对象资源, 再匹配访问方法
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
; 超级用户 root 直接通过
m = r.sub == 'root' || r.sub == p.sub && r.obj == p.obj && r.act == p.act
; keyMatch2 是 url : 匹配模式
; p, admin, /book/:id, GET
; 当请求 admin, /book/2, GET 返回 true
; regexMatch 是字符正则表达式 当 act 包含 GET 就通过
; https://casbin.org/zh/docs/function 中有函数的介绍
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
添加自定义匹配函数, 实现匹配act
大小写匹配,以go
语言为例。
import (
"fmt"
"strings"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
)
func main() {
model := model.NewModel()
model.AddDef("r", "r", "sub, obj, act")
model.AddDef("p", "p", "sub, obj, act")
model.AddDef("g", "g", "_, _")
model.AddDef("e", "e", "some(where (p.eft == allow))")
model.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && method(r.act, p.act)")
model.AddPolicy("p", "p", []string{"admin", "/api/users", "post|get"})
model.AddPolicy("p", "p", []string{"users", "/api/users", "get"})
e, _ := casbin.NewEnforcer(model)
e.AddGroupingPolicy("guest", "admin")
e.AddGroupingPolicy("user", "users")
e.AddFunction("method", MethodMatchFunc)
fmt.Println(e.EnforceEx("guest", "/api/users", "POST"))
fmt.Println(e.EnforceEx("guest", "/api/users", "GET"))
}
func MethodMatch(key1 string, key2 string) bool {
if key2 == "*" {
return true
}
if strings.Contains(key2, "|") {
methods := strings.Split(key2, "|")
if len(methods) > 0 {
for _, method := range methods {
if strings.ToUpper(method) == strings.ToUpper(key1) {
return true
}
}
}
}
return strings.ToUpper(key1) == strings.ToUpper(key2)
}
func MethodMatchFunc(args ...interface{}) (interface{}, error) {
ract := args[0].(string)
pact := args[1].(string)
return (bool)(MethodMatch(ract, pact)), nil
}
php
版本的实现
<?php
require_once './vendor/autoload.php';
use Casbin\Enforcer;
use Casbin\Model\Model;
$model = new Model;
$model->addDef("r", "r", "sub, obj, act");
$model->addDef("p", "p", "sub, obj, act");
$model->addDef("g", "g", "_, _");
$model->addDef("e", "e", "some(where (p.eft == allow))");
$model->addDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && method(r.act, p.act)");
$model->addPolicy("p", "p", ["admin", "/api/users", "post|get"]);
$model->addPolicy("p", "p", ["users", "/api/users", "get"]);
$e = new Enforcer($model);
$e->addFunction('method', static function(...$args){
$ract = $args[0];
$pact = $args[1];
if($pact == '*'){
return true;
}
if(str_contains($pact, "|")){
$methods = explode("|", $pact);
foreach($methods as $method){
if(strtoupper($method) == strtoupper($ract)){
return true;
}
}
}
return strtoupper($ract) == strtolower($pact);
});
$e->addGroupingPolicy("guest", "admin");
$e->addGroupingPolicy("user", "users");
var_dump($e->enforceEx("guest", "/api/users", "POST"));
var_dump($e->enforceEx("user", "/api/users", "GET"));
其中model
中的matchers
可以定义成
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && method(r.act, p.act)
matchers
可以自定义函数也可以使用系统提供的函数,这里用eval
来举个例子
eval
是一种条件判断语句,可以自定义policy<222> 中的字段,然后通过
policy
字段表达式来验证。譬如
model.conf
文件
[request_definition]
r = sub, obj, act
[policy_definition]
; 这里的 sub_rule 是我们自定义的字段,这里后期在策略里写条件表达式
p = sub_rule, sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
; 验证 sub 的 Group 群组是否等于 p.sub
; policy 中写入 r.sub.Method == 'edit' 来验证这个用户是否有编辑权限
m = eval(sub_rule) && r.sub.Group == p.sub && r.act == p.act && r.obj == p.obj
policy.csv
文件
p, r.sub.Method == "edit", admin, /api/user/edit, post
p, r.sub.Method == "delete", admin, /api/user/edit, post
go
源码测试文件
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
enfor, _ := casbin.NewEnforcer(`./casbin/model.conf`, `./casbin/policy.csv`)
sub := struct {
Group string
Method string
}{
Group: "admin",
Method: "edit",
}
fmt.Println(enfor.EnforceEx(sub, "/api/user/edit", "post"))
fmt.Println(enfor.EnforceEx(sub, "/api/user/delete", "post"))
}
基本上基础内容就这些,剩下的就是根据api
接口集成的adapter
适配器,php
、go
等都有可以从官网看,然后每个适配器都有文档,很简单这里就不啰嗦了,有需要或者不懂的可以留言。
发表评论 取消回复