天天看點

casbin學習筆記casbin

與你相識

casbin學習筆記casbin

部落客介紹:

– 本人是普通大學生一枚,每天鑽研計算機技能,CSDN主要分享一些技術内容,因我常常去尋找資料,不經常能找到合适的,精品的,全面的内容,導緻我花費了大量的時間,是以會将摸索的内容全面細緻記錄下來。另外,我更多關于管理,生活的思考會在簡書中釋出,如果你想了解我對生活有哪些反思,探索,以及對管理或為人處世經驗的總結,我也歡迎你來找我。

– 目前的學習專注于Go語言,輔學算法,前端領域。也會分享一些校内課程的學習,例如資料結構,計算機組成原理等等,如果你喜歡我的風格,請關注我,我們一起成長。

Table of Contents

  • casbin
    • 官方文檔
      • 基礎知識
        • 安裝及簡單實用
        • 工作原理
      • Model
        • 支援的Model
        • Model文法
          • Request定義
          • Policy定義
          • Policy effect定義
          • Matchers
            • 内置函數
        • RBAC
        • ABAC
    • 每日一庫——casbin
      • ACL模型
      • RBAC模型
        • 多個RBAC
        • 多層角色
        • RBAC domain
      • ABAC模型
      • 模型存儲
      • 政策存儲
        • 使用函數
    • 參考資料

casbin

權限管理幾乎在每個系統中都是必備的子產品。如果項目開發每次都要實作一次權限管理,會浪費很多的時間,而casbin就來做這個事情,支援常用的多種通路控制模型,如

ACL/RBAC/ABAC

等。可以實作靈活的通路權限控制。

比如說什麼角色,什麼使用者,可以通過某個api,就可以使用casbin來進行限制。

官方文檔

由于官方文檔到中間靠後的部分有點難以了解,是以就轉向其它教程了。

基礎知識

安裝及簡單實用

go get github.com/casbin/casbin
           

另外建立一個Casbin決策器需要有一個模型檔案和政策檔案作為參數:

import "github.com/casbin/casbin"

e := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
           

然後可以在通路發生之前,給代碼添加強制挂鈎:

sub := "alice" // 想要通路資源的使用者。
obj := "data1" //要通路的資源。
act := "read" // 使用者對資源執行的操作。

if e.Enforce(sub, obj, act) == true {
    // 允許alice讀取data1
} else {
    // 拒絕請求,顯示錯誤
}
           

工作原理

casbin的通路控制模型被抽象為基于PERM(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
   // 對于過長的單元配置,也可以通過在結尾處添加“/”進行斷行
    m = r.sub == p.sub && r.obj == p.obj \ 
  && r.act == p.act
           

policy

如下:

// 這表示alice可以讀取data1
    p, alice, data1, read
// 這表示bob可以編寫data2
    p, bob, data2, write
           

Model

支援的Model

  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文法

  • Model CONF 至少應包含四個部分:

    [request_definition], [policy_definition], [policy_effect], [matchers]

  • 如果 model 使用 RBAC, 還需要添加

    [role_definition]

    部分。
Request定義

[request_definition]

部分用于request的定義,它明确了

e.Enforce(...)

函數中參數的含義。

[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
           

這些是我們對policy規則的具體描述

p, alice, data1, read
           

policy部分的每一行稱之為一個政策規則。 上面的policy的綁定關系将會在matcher中使用, 羅列如下:

Policy effect定義

[policy_effect]

部分是對policy生效範圍的定義, 原語定義了當多個policy rule同時比對通路請求request時,該如何對多個決策結果進行內建以實作統一決策。 以下示例展示了一個隻有一條規則生效,其餘都被拒絕的情況:

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

該Effect原語表示如果存在任意一個決策結果為

allow

的比對規則,則最終決策結果為

allow

。 其中

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原語支援+、 -、 *、 /等算數運算符,==,、!=、 >、 <等關系運算符以及&& (與)、|| (或)、 ! (非)等邏輯運算符。

内置函數

函數 釋義 示例
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

也可以添加自定義函數,具體參考:添加自定義函數

RBAC

看不懂

ABAC

看不懂

每日一庫——casbin

ACL模型

下面可以看到我們使用ACL模型進行的權限控制

我們依然使用 Go Module 編寫代碼,先初始化:

$ mkdir casbin && cd casbin
$ go mod init github.com/darjun/go-daily-lib/casbin
           

然後安裝

casbin

,目前是

v2

版本:

$ go get github.com/casbin/casbin/v2
           

權限實際上就是控制誰能對什麼資源進行什麼操作。

casbin

将通路控制模型抽象到一個基于 PERM(Policy,Effect,Request,Matchers) 元模型的配置檔案(模型檔案)中。是以切換或更新授權機制隻需要簡單地修改配置檔案。

policy

是政策或者說是規則的定義。它定義了具體的規則。

request

是對通路請求的抽象,它與

e.Enforce()

函數的參數是一一對應的

matcher

比對器會将請求與定義的每個

policy

一一比對,生成多個比對結果。

effect

根據對請求運用比對器得出的所有結果進行彙總,來決定該請求是允許還是拒絕。

編寫模型檔案:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

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

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

上面模型檔案規定了權限由

sub,obj,act

三要素組成,隻有在政策清單中有和它完全相同的政策時,該請求才能通過。比對器的結果可以通過

p.eft

擷取,

some(where (p.eft == allow))

表示隻要有一條政策允許即可。

然後我們政策檔案(即誰能對什麼資源進行什麼操作):

p, dajun, data1, read
p, lizi, data2, write
           

上面

policy.csv

檔案的兩行内容表示

dajun

對資料

data1

read

權限,

lizi

對資料

data2

write

權限。

接下來就是使用的代碼:

package main

import (
  "fmt"
  "log"

  "github.com/casbin/casbin/v2"
)

func check(e *casbin.Enforcer, sub, obj, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s\n", sub, act, obj)
  } else {
    fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}
           

代碼其實不複雜。首先建立一個

casbin.Enforcer

對象,加載模型檔案

model.conf

和政策檔案

policy.csv

,調用其

Enforce

方法來檢查權限。運作程式:

$ go run main.go
dajun CAN read data1
lizi CAN write data2
dajun CANNOT write data1
dajun CANNOT read data2
           

請求必須完全比對某條政策才能通過。

("dajun", "data1", "read")

比對

p, dajun, data1, read

("lizi", "data2", "write")

比對

p, lizi, data2, write

,是以前兩個檢查通過。第 3 個因為

"dajun"

沒有對

data1

write

權限,第 4 個因為

dajun

data2

沒有

read

權限,是以檢查都不能通過。輸出結果符合預期。

sub/obj/act

依次對應傳給

Enforce

方法的三個參數。實際上這裡的

sub/obj/act

read/write/data1/data2

是我自己随便取的,你完全可以使用其它的名字,隻要能前後一緻即可。

上面例子中實作的就是

ACL

(access-control-list,通路控制清單)。

ACL

顯示定義了每個主體對每個資源的權限情況,未定義的就沒有權限。我們還可以加上超級管理者,超級管理者可以進行任何操作。假設超級管理者為

root

,我們隻需要修改比對器:

[matchers]
e = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"
           

隻要通路主體是

root

一律放行。

驗證:

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "root", "data1", "read")
  check(e, "root", "data2", "write")
  check(e, "root", "data1", "execute")
  check(e, "root", "data3", "rwx")
}
           

因為

sub = "root"

時,比對器一定能通過,運作結果:

$ go run main.go
root CAN read data1
root CAN write data2
root CAN execute data1
root CAN rwx data3
           

RBAC模型

ACL

在使用者和資源比較少的情況下沒有什麼問題,但是如果使用者很多的話,就會顯得非常的繁瑣,試想一下每次新增一個使用者,都要把他需要的權限重新設定一遍是多麼地痛苦。

是以

RBAC

就通過引入

角色(role)

來解決這個問題,每個使用者都屬于一個角色,每個角色都有其特定的權限,這樣新增使用者的時候,隻需要給他指派一個角色,他就可以擁有該角色對應的權限資訊。

使用

RBAC

模型需要在

ACL

MODEL的基礎上添加

role_definition

子產品:

g = _,_

定義了使用者——角色,角色——角色的映射關系,前者是後者的成員,擁有後者的權限。然後在比對器中,我們不需要判斷

r.sub

p.sub

是否相當,隻需要使用

g(r.sub, p.sub)

來判斷請求主體

r.sub

是否屬于

p.sub

這個角色即可。

[role_definition]
g = _, _

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

最後再修改政策檔案,下面的檔案就規定了

dajun

屬于

admin

管理者,

lizi

屬于

developer

開發者,使用

g

來定義這層關系。

另外具體的角色,如

admin

對資料data有rede和write權限也通過

p

來定義。

p, admin, data, read
p, admin, data, write
p, developer, data, read
g, dajun, admin
g, lizi, developer
           

我們編寫主程式

package main

import (
  "fmt"
  "log"

  "github.com/casbin/casbin/v2"
)

func check(e *casbin.Enforcer, sub, obj, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s\n", sub, act, obj)
  } else {
    fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "data", "read")
  check(e, "dajun", "data", "write")
  check(e, "lizi", "data", "read")
  check(e, "lizi", "data", "write")
}
           

結果:

dajun CAN read data
dajun CAN write data
lizi CAN read data
lizi CANNOT write data
           

多個RBAC

可以為使用者和資源都賦予角色的概念

[role_definition]
g=_,_
g2=_,_

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

上面的模型檔案定義了兩個

RBAC

系統

g

g2

,我們在比對器中使用

g(r.sub, p.sub)

判斷請求主體屬于特定組,

g2(r.obj, p.obj)

判斷請求資源屬于特定組,且操作一緻即可放行。

p, admin, prod, read
p, admin, prod, write
p, admin, dev, read
p, admin, dev, write
p, developer, dev, read
p, developer, dev, write
p, developer, prod, read
g, dajun, admin
g, lizi, developer
g2, prod.data, prod
g2, dev.data, dev
           

先看角色關系,即最後 4 行,

dajun

屬于

admin

角色,

lizi

屬于

developer

角色,

prod.data

屬于生産資源

prod

角色,

dev.data

屬于開發資源

dev

角色。

admin

角色擁有對

prod

dev

類資源的讀寫權限,

developer

隻能擁有對

dev

的讀寫權限和

prod

的讀權限。

check(e, "dajun", "prod.data", "read")
check(e, "dajun", "prod.data", "write")
check(e, "lizi", "dev.data", "read")
check(e, "lizi", "dev.data", "write")
check(e, "lizi", "prod.data", "write")
           
dajun CAN read prod.data
dajun CAN write prod.data
lizi CAN read dev.data
lizi CAN write dev.data
lizi CANNOT write prod.data
           

多層角色

可以為角色定義所屬角色,這種權限關系可以進行傳遞。

例如

dajun

屬于進階開發者

senior

seinor

屬于開發者,那麼

dajun

也是開發者,擁有開發者的所有權限,那麼我們就可以定義開發者共有的權限,然後額外為進階開發者

senior

定義一些特殊的權限。

模型檔案不需要修改,政策檔案改動如下:

# 定義了senior對資料data有write權限
p, senior, data, write
# 定義了developer對資料data有read權限
p, developer, data, read
# 為dajun賦予senior權限
g, dajun, senior
# 為senior賦予developer權限,senior權限本身對data有write權限,又被加了一個developer權限,就可寫可讀了。
g, senior, developer
# 為lizi賦予developer權限
g, lizi, developer
           

RBAC domain

角色可以是全局的,也可以是特定域(domain)的或租戶(tenant),可以了解為

例如

dajun

在組

tenant1

中是管理者,擁有很高的權限,但是在

tenant2

中可能隻是一個底層人員。

使用

RBAC domain

需要對模型檔案做以下修改:

[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act

[role_definition]
g = _,_,_

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

g=_,_,_

表示前者在後者中擁有中間定義的角色,在比對器中使用

g

要帶上

dom

# admin在tenant1中對data1有read權限
p, admin, tenant1, data1, read
# admin在tenant2中對data2有read權限
p, admin, tenant2, data2, read
# dajun在tenant1中的角色是admin
g, dajun, admin, tenant1
# dajun在tenant2中的角色是developer
g, dajun, developer, tenant2
           

然後我們編寫主程式

func check(e *casbin.Enforcer, sub, domain, obj, act string) {
  ok, _ := e.Enforce(sub, domain, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s in %s\n", sub, act, obj, domain)
  } else {
    fmt.Printf("%s CANNOT %s %s in %s\n", sub, act, obj, domain)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "tenant1", "data1", "read")
  check(e, "dajun", "tenant2", "data2", "read")
}
           

結果不出意料:

dajun CAN read data1 in tenant1
dajun CANNOT read data2 in tenant2
           

ABAC模型

RBAC

模型對于實作比較規則的、相對靜态的權限管理比較好用,但是例如我們需要在不同的時間段對資料

data

實作不同的權限控制,比如正常的工作時間

9:00-18:00

所有人都可以讀寫

data

,其它時間隻有資料所有者能讀寫。這種需求就需要使用

ABAC

模型完成。

首先修改

model

檔案:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[matchers]
m = r.sub.Hour >= 9 && r.sub.Hour < 18 || r.sub.Name == r.obj.Owner

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

ABAC

模型不需要政策檔案:

type Object struct {
  Name  string
  Owner string
}

type Subject struct {
  Name string
  Hour int
}

func check(e *casbin.Enforcer, sub Subject, obj Object, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s at %d:00\n", sub.Name, act, obj.Name, sub.Hour)
  } else {
    fmt.Printf("%s CANNOT %s %s at %d:00\n", sub.Name, act, obj.Name, sub.Hour)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  o := Object{"data", "dajun"}
  s1 := Subject{"dajun", 10}
  check(e, s1, o, "read")

  s2 := Subject{"lizi", 10}
  check(e, s2, o, "read")

  s3 := Subject{"dajun", 20}
  check(e, s3, o, "read")

  s4 := Subject{"lizi", 20}
  check(e, s4, o, "read")
}
           
dajun CAN read data at 10:00
lizi CAN read data at 10:00
dajun CAN read data at 20:00
lizi CANNOT read data at 20:00
           

使用

ABAC

模型可以非常靈活的進行權限控制,但是在一般情況的

RBAC

就夠用了。

模型存儲

casbin可以實作在代碼中動态初始化模型:

func main() {
  m := model.NewModel()
  m.AddDef("r", "r", "sub, obj, act")
  m.AddDef("p", "p", "sub, obj, act")
  m.AddDef("e", "e", "some(where (p.eft == allow))")
  m.AddDef("m", "m", "r.sub == g.sub && r.obj == p.obj && r.act == p.act")

  a := fileadapter.NewAdapter("./policy.csv")
  e, err := casbin.NewEnforcer(m, a)
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}
           

或者在字元串中加載

func main() {
  text := `
  [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
  `

  m, _ := model.NewModelFromString(text)
  a := fileadapter.NewAdapter("./policy.csv")
  e, _ := casbin.NewEnforcer(m, a)

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}
           

但是這兩種方式都不推薦使用

政策存儲

在前面的例子中,我們都是将政策存儲在一個.csv檔案中,但是實際應用中一般不用這種方式存儲,

casbin

以第三方擴充卡的方式支援多種存儲方式,包括

Mysql/MongoDB/Redis/Etcd

等。

CREATE DATABASE IF NOT EXISTS casbin;

USE casbin;

CREATE TABLE IF NOT EXISTS casbin_rule (
  p_type VARCHAR(100) NOT NULL,
  v0 VARCHAR(100),
  v1 VARCHAR(100),
  v2 VARCHAR(100),
  v3 VARCHAR(100),
  v4 VARCHAR(100),
  v5 VARCHAR(100)
);

INSERT INTO casbin_rule VALUES
('p', 'dajun', 'data1', 'read', '', '', ''),
('p', 'lizi', 'data2', 'write', '', '', '');
           

然後使用

Gorm Adapter

加載

policy

Gorm Adapter

預設使用

casbin

庫中的

casbin_rule

表:

package main

import (
  "fmt"

  "github.com/casbin/casbin/v2"
  gormadapter "github.com/casbin/gorm-adapter/v2"
  _ "github.com/go-sql-driver/mysql"
)

func check(e *casbin.Enforcer, sub, obj, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s\n", sub, act, obj)
  } else {
    fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
  }
}

func main() {
  a, _ := gormadapter.NewAdapter("mysql", "root:[email protected](127.0.0.1:3306)/")
  e, _ := casbin.NewEnforcer("./model.conf", a)

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}
           

運作:

dajun CAN read data1
lizi CAN write data2
dajun CANNOT write data1
dajun CANNOT read data2
           

使用函數

我們可以在比對器中使用函數。

casbin

内置了一些函數

keyMatch/keyMatch2/keyMatch3/keyMatch4

都是比對 URL 路徑的,

regexMatch

使用正則比對,

ipMatch

比對 IP 位址。參見https://casbin.org/docs/en/function。使用内置函數我們能很容易對路由進行權限劃分:

[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && r.act == p.act
p, dajun, user/dajun/*, read
p, lizi, user/lizi/*, read
           

不同使用者隻能通路其對應路由下的 URL:

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "user/dajun/1", "read")
  check(e, "lizi", "user/lizi/2", "read")
  check(e, "dajun", "user/lizi/1", "read")
}
           

輸出:

dajun CAN read user/dajun/1
lizi CAN read user/lizi/2
dajun CANNOT read user/lizi/1
           

我們當然也可以定義自己的函數。先定義一個函數,傳回 bool:

func KeyMatch(key1, 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
}
           

然後添加到權限認證器中:

e.AddFunction("my_func", KeyMatchFunc)
           

這樣我們就可以在比對器中使用該函數實作正則比對了:

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

接下來我們在政策檔案中為

dajun

賦予權限:

p, dajun, data/*, read
           

dajun

對比對模式

data/*

的檔案都有

read

權限。

驗證一下:

check(e, "dajun", "data/1", "read")
check(e, "dajun", "data/2", "read")
check(e, "dajun", "data/1", "write")
check(e, "dajun", "mydata", "read")
           

dajun

data/1

沒有

write

權限,

mydata

不符合

data/*

模式,也沒有

read

權限:

dajun CAN read data/1
dajun CAN read data/2
dajun CANNOT write data/1
dajun CANNOT read mydata
           

參考資料

  • casbin中文開發文檔(官方)
  • 每日一庫——casbin

歡迎評論區讨論,或指出問題。 如果覺得寫的不錯,歡迎點贊,轉發,收藏。