上一篇講了搭建一個身份認證系統,可以看到借助dex搭建一個安全可靠的身份認證系統并不是太難。本篇再講一下用
casbin
完成驗證授權。
什麼是驗證授權
授權(英語:Authorization)一般是指對資訊安全或計算機安全相關的資源定義與授予通路權限,尤指通路控制。動詞“授權”可指定義通路政策與接受通路。
授權
作為名詞,其代表的是在計算機系統中定義的資源通路權限。而
驗證授權
就是驗證計算機帳戶是否有資源的通路權限。
舉個栗子,假設現在有一本書
book1
,其擁有
read
,
write
的操作,那麼我們可以先定義以下
授權
:
-
可以alice
書籍read
book1
-
可以bob
書籍write
book1
-
可以bob
書籍read
book1
現在來了一個使用者
alice
她想
write
書籍
book1
,這時調用驗證授權功能子產品的接口,驗證授權功能子產品根據上述
授權
規則可以快速判斷
alice
不可以
write
書籍
book1
;過一會兒又來了一個使用者
bob
他想
write
書籍
book1
,這時調用驗證授權系統的接口,驗證授權系統根據上述
授權
規則可以快速判斷
bob
可以
write
書籍
book1
。
可以看到
身份認證系統
強調地是安全可靠地得到計算機使用者的身份資訊,而
驗證授權
強調地是根據計算機的身份資訊、通路的資源、對資源的操作等給出一個Yes/No的答複。
常用授權模型
ACL
ACL
是
Access Control List
的縮寫,稱為通路控制清單. 定義了誰可以對某個資料進行何種操作. 關鍵資料模型有: 使用者, 權限.
ACL規則簡單, 也帶來一些問題: 資源的權限需要在使用者間切換的成本極大; 使用者數或資源的數量增長, 都會加劇規則維護成本;
典型應用
- 檔案系統
檔案系統的檔案或檔案夾定義某個賬号(user)或某個群組(group)對檔案(夾)的讀(read)/寫(write)/執行(execute)權限.
- 網絡通路
防火牆: 伺服器限制不允許指定機器通路其指定端口, 或允許特定指定伺服器通路其指定幾個端口.
RBAC
RBAC
是
Role-based access control
的縮寫, 稱為 基于角色的通路控制. 核心資料模型有: 使用者, 角色, 權限.
使用者具有角色, 而角色具有權限, 進而表達使用者具有權限.
由于有角色作為中間紐帶, 當新增使用者時, 隻需要為使用者賦予角色, 使用者即獲得角色所包含的所有權限.
RBAC
存在多個擴充版本,
RBAC0
、
RBAC1
、
RBAC2
、
RBAC3
。這些版本的詳細說明可以參數這裡。我們在實際項目中經常使用的是
RBAC1
,即帶有角色繼承概念的RBAC模型。
ABAC
ABAC
是
Attribute-based access control
的縮寫, 稱為基于屬性的通路控制.
權限和資源當時的狀态(屬性)有關, 屬性的值可以用于正向判斷(符合某種條件則通過), 也可以用于反向判斷(符合某種條件則拒絕):
典型應用
- 論壇的評論權限, 當文章是鎖定狀态時, 則不再允許繼續評論;
- Github 私有倉庫不允許其他人通路;
- 發帖者可以編輯/删除評論(如果是RBAC, 會為發帖者定義一個角色, 但是每個文章都要新增一條使用者/發帖角色的記錄);
- 微信聊天消息超過2分鐘則不再允許撤回;
- 12306 隻有實名認證後的賬号才能購票;
- 已過期的付費賬号将不再允許使用付費功能;
實作權限驗證
前面提到了多種不同的權限模型,要完全自研實作不同的權限模型還是挺麻煩的。幸好
casbin
出現,它将上述不同的模型抽象為自己的
PERM metamodel
,這個
PERM metamodel
隻包括
Policy
,
Effect
,
Request
,
Matchers
,隻通過這幾個模型對象的組合即可實作上述提到的多種權限模型,如果業務上需要切換權限模型,也隻需要配置一下
PERM metamodel
,并不需要大改權限模型相關的代碼,這一點真的很贊!!!
一個最簡單的
ACL
權限模型即可像下面這樣定義:
acl_simple_model.conf
# Request definition
[request_definition]
r = sub, obj, act
# Policy definition
[policy_definition]
p = sub, obj, act
# Policy effect
[policy_effect]
e = some(where (p.eft == allow))
# Matchers
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
複制
相應的授權規則可以像下面這樣定義:
acl_simple_policy.csv
p, alice, data1, read
p, bob, data2, write
複制
這意味着
alice
可以
read
資源
data1
;
bob
可以
write
資源
data2
。
寫一個簡單的程式就可以完成該權限驗證:
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
e, _ := casbin.NewSyncedEnforcer("acl_simple_model.conf", "acl_simple_policy.csv")
sub := "alice" // the user that wants to access a resource.
obj := "data1" // the resource that is going to be accessed.
act := "read" // the operation that the user performs on the resource.
if passed, _ := e.Enforce(sub, obj, act); passed {
// permit alice to read data1
fmt.Println("Enforce policy passed.")
} else {
// deny the request, show an error
fmt.Println("Enforce policy denied.")
}
}
複制
casbin模型詳解
casbin
官方其實已經提供了多種模型的定義及示例policy定義,見這裡。而且為了便于使用者了解診斷模型及policy,還給了個線上的editor,修改模型或policy時可以利用此工具。
從上面的示例可以看出基于
casbin
實作權限驗證,代碼很簡單,但
casbin
的模型定義及policy定義初看還是挺複雜的,這樣詳細了解一下。
casbin
的模型定義裡會出現4個部分:
[request_definition]
,
[policy_definition]
,
[policy_effect]
,
[matchers]
。
其中
[request_definition]
描述的是通路請求的定義,如下面的定義将通路請求的三個參數分别映射為
r.sub
、
r.obj
、
r.act
(注意并不是所有的通路請求一定是3個參數):
[request_definition]
r = sub, obj, act
複制
同理
[policy_definition]
描述的是授權policy的定義,如下面的定義将每條授權policy分别映射為
p.sub
、
p.obj
、
p.act
(注意并不是所有的授權policy一定是3個參數,也不是必須隻有一條授權policy定義):
[policy_definition]
p = sub, obj, act
複制
[matchers]
描述的是根據通路請求如何找到比對的授權policy,如下面的定義将根據
r.sub
、
r.obj
、
r.act
、
p.sub
、
p.obj
、
p.act
找到完全比對的授權policy:
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
複制
在寫
[matchers]
規則是還可以使用一些内置或自定義函數,參考這裡的文檔。
最後
[policy_effect]
描述如果找到比對的多條的授權policy,最終給出的驗證授權結果,如下面的定義說明隻要有一條比對的授權政策其
eft
是
allow
,則最終給出的驗證授權結果就是
允許
(注意每條授權policy預設的eft就是allow)。
[policy_effect]
e = some(where (p.eft == allow))
複制
如果使用
RBAC
權限模型,可能還會使用
[role_definition]
,這個
[role_definition]
算是最複雜的了,其可以描述user-role之間的映射關系或resource-role之間的映射關系。這麼說比較抽象,還是舉例說明:
假設模型定義如下:
[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
複制
授權policy檔案如下:
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin
複制
現在收到了授權請求
alice, data2, read
,這時
r.sub
為
alice
,根據
g = _, _
及
g(r.sub, p.sub)
,我們可以得出對應的
p.sub
可以為
data2_admin
,接下來再根據
r.obj == p.obj && r.act == p.act
,最終找到比對的授權policy規則為
p, data2_admin, data2, read
,最後根據
some(where (p.eft == allow))
規則,此時驗證授權的結果就應該是
allow
。
這裡
casbin
根據
r.sub
查找對應
p.sub
的過程還會考慮角色繼承。考慮以下授權policy檔案:
p, reader, data2, read
p, writer, data2, write
g, admin, reader
g, admin, writer
g, alice, admin
複制
現在收到了授權請求
alice, data2, read
,這時
r.sub
為
alice
,根據
g = _, _
及
g(r.sub, p.sub)
,我們可以得出對應的
p.sub
可以為
admin
,
reader
,
writer
,接下來再根據
r.obj == p.obj && r.act == p.act
,最終找到比對的授權policy規則為
p, reader, data2, read
,最後根據
some(where (p.eft == allow))
規則,此時驗證授權的結果就應該是
allow
。
通過
[role_definition]
還可以定義resource-role之間的映射關系,見示例。
casbin
的模型大概就是上面描述的了,很多概念了解起來可能比較費勁,結合示例及在editor上做些實驗應該了解得更快一些。
casbin相關事項
-
的模型定義及授權policy定義還可以選擇儲存在其它存儲中,見Model Storage、Policy Storage、Adapters。casbin
-
的casbin
對象還提供了一系列API接口管理授權policy規則,見Management API、RBAC API。Enforcer
- 可以修改授權policy規則,那麼當多個驗證授權服務分布式部署時,必然要考慮某個服務修改了授權規則後,其它服務示例必須進行規則的同步。
也考慮到了這個需求,提供了Watchers機制,用于在觀察到授權規則發生變更時進行必要的回調,見Watchers。casbin
- 在多線程環境下使用
對象的接口,必須使用Enforcer
建立casbin.NewSyncedEnforcer
,另外還支援授權policyEnforcer
特性,見這裡。AutoLoad
-
預設是從授權policy檔案中加載角色及角色的繼承資訊的,也可以從其它外部資料源擷取這些資訊,見這裡。casbin
casbin代碼分析
casbin
整體代碼很簡單,很多代碼都是模型定義及授權policy定義加載的邏輯,關鍵代碼隻有一個方法Enforce,見下面:
if !e.enabled {
return true, nil
}
functions := make(map[string]govaluate.ExpressionFunction)
for key, function := range e.fm {
functions[key] = function
}
if _, ok := e.model["g"]; ok {
for key, ast := range e.model["g"] {
rm := ast.RM
functions[key] = util.GenerateGFunction(rm)
}
}
expString := e.model["m"]["m"].Value
expression, err := govaluate.NewEvaluableExpressionWithFunctions(expString, functions)
if err != nil {
return false, err
}
rTokens := make(map[string]int, len(e.model["r"]["r"].Tokens))
for i, token := range e.model["r"]["r"].Tokens {
rTokens[token] = i
}
pTokens := make(map[string]int, len(e.model["p"]["p"].Tokens))
for i, token := range e.model["p"]["p"].Tokens {
pTokens[token] = i
}
parameters := enforceParameters{
rTokens: rTokens,
rVals: rvals,
pTokens: pTokens,
}
if policyLen := len(e.model["p"]["p"].Policy); policyLen != 0 {
policyEffects = make([]effect.Effect, policyLen)
matcherResults = make([]float64, policyLen)
if len(e.model["r"]["r"].Tokens) != len(rvals) {
return false, errors.New(
fmt.Sprintf(
"invalid request size: expected %d, got %d, rvals: %v",
len(e.model["r"]["r"].Tokens),
len(rvals),
rvals))
}
for i, pvals := range e.model["p"]["p"].Policy {
// log.LogPrint("Policy Rule: ", pvals)
if len(e.model["p"]["p"].Tokens) != len(pvals) {
return false, errors.New(
fmt.Sprintf(
"invalid policy size: expected %d, got %d, pvals: %v",
len(e.model["p"]["p"].Tokens),
len(pvals),
pvals))
}
parameters.pVals = pvals
result, err := expression.Eval(parameters)
// log.LogPrint("Result: ", result)
if err != nil {
return false, err
}
switch result := result.(type) {
case bool:
if !result {
policyEffects[i] = effect.Indeterminate
continue
}
case float64:
if result == 0 {
policyEffects[i] = effect.Indeterminate
continue
} else {
matcherResults[i] = result
}
default:
return false, errors.New("matcher result should be bool, int or float")
}
if j, ok := parameters.pTokens["p_eft"]; ok {
eft := parameters.pVals[j]
if eft == "allow" {
policyEffects[i] = effect.Allow
} else if eft == "deny" {
policyEffects[i] = effect.Deny
} else {
policyEffects[i] = effect.Indeterminate
}
} else {
policyEffects[i] = effect.Allow
}
if e.model["e"]["e"].Value == "priority(p_eft) || deny" {
break
}
}
}
複制
這個代碼邏輯很清楚了,就是根據
[matchers]
、
[request_definition]
、
[policy_definition]
找到比對的
[policy_definition]
,再根據
[policy_effect]
最後得出最終的驗證授權結果。可以看到該處理邏輯裡大量地周遊了
e.model["r"]["r"].Tokens
、
e.model["p"]["p"].Tokens
、
e.model["p"]["p"].Policy
,當授權policy規則條數較多時,估計性能不會太好。但官方給了個性能測試報告,據說性能還可以,這個後面還須再驗證下。
為了優化性能,其實是可以将驗證授權操作的結果進行緩存,官方也提供了CachedEnforcer,目測邏輯沒問題,如果确實遇到性能瓶頸,可以考慮采用。
其它外部支援
一些開源愛好者為
casbin
貢獻了很多中間件元件,便于在多個程式設計語言中內建
casbin
進行權限驗證。
還有一些開源愛好者為
casbin
貢獻了模型管理及授權政策管理的web前端,如果覺得手工修改授權政策檔案不直覺的話,可以考慮采用。
還可以看到目前很多開源項目的權限驗證部分都是采用了
casbin
來實作的,例如harbor裡的rbac權限驗證。
還發現一個基于
casbin
實作的身份認證及驗證授權服務,這個例子以後可以好好參考一下。
自己研究
casbin
的示例項目。
參考
- https://github.com/isayme/blog/issues/34
- https://www.jianshu.com/p/b078abe9534f
- https://casbin.org/docs/en/overview
- https://casbin.org/docs/en/supported-models
- https://casbin.org/docs/en/syntax-for-models
- https://casbin.org/docs/en/rbac
- https://casbin.org/docs/en/model-storage
- https://casbin.org/docs/en/policy-storage
- https://casbin.org/docs/en/adapters
- https://casbin.org/docs/en/management-api
- https://casbin.org/docs/en/rbac-api
- https://casbin.org/docs/en/watchers
- https://casbin.org/docs/en/role-managers
- https://github.com/casbin/casbin
- https://casbin.org/docs/en/benchmark
- https://casbin.org/docs/en/middlewares
- https://casbin.org/docs/en/admin-portal
- https://casbin.org/docs/en/adopters
- https://github.com/Soontao/go-simple-api-gateway