request_definition 请求定义

请求定义,r = {sub, obj, act} 或者是 r = {sub, dom, obj, act}
  1. sub 请求主体,譬如是 admin 这个用户来请求。
  2. dom 在多商户情况下使用,代表不同的域。譬如 agent 代理。
  3. obj 访问对象,譬如 /api/getUser 这个链接。
  4. 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 可以省略,默认情况下的 eftallow。各个解释的含义和 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) ,则在 policyg, 角色, 用户

[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.usertest_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 匹配器

通过对 requestpolicyrole 匹配,来返回匹配到的 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 适配器,phpgo 等都有可以从官网看,然后每个适配器都有文档,很简单这里就不啰嗦了,有需要或者不懂的可以留言。

点赞(1) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部