天天看點

從别人的代碼中學習golang系列--03

這篇部落格還是整理從https://github.com/LyricTian/gin-admin 這個項目中學習的golang相關知識。

作者在項目中使用了

github.com/casbin/casbin

進行權限控制的,這個庫自己之前也沒有用過,正好可以通過這個項目學習一下使用。 當然這篇部落格并不會對casbin的使用做非常詳細的說明,感興趣的可以去官網看具體的使用文檔。

關于casbin

常見通路控制模型

ABAC: 基于屬性的通路控制。

DAC: 自主通路控制模型(DAC,Discretionary Access Control)是根據自主通路控制政策建立的一種模型,允許合法使用者以使用者或使用者組的身份通路政策規定的客體,同時阻止非授權使用者通路客體。擁有客體權限的使用者,可以将該客體的權限配置設定給其他使用者。

ACL:  ACL是最早也是最基本的一種通路控制機制,它的原理非常簡單:每一項資源,都配有一個清單,這個清單記錄的就是哪些使用者可以對這項資源執行CRUD中的那些操作。當系統試圖通路這項資源時,會首先檢查這個清單中是否有關于目前使用者的通路權限,進而确定目前使用者可否執行相應的操作。總得來說,ACL是一種面向資源的通路控制模型,它的機制是圍繞“資源”展開的。

RBAC: 基于角色的通路控制(RBAC, Role Based Access Control)在使用者和權限之間引入了“角色(Role)”的概念,角色解耦了使用者和權限之間的關系。

casbin

casbin使用配置檔案來設定通路控制模型。在 Casbin 中, 通路控制模型被抽象為基于 PERM (Policy, Effect, Request, Matcher) 的一個檔案。

Casbin中最基本、最簡單的model是ACL。ACL中的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
           

Request definition: 代表請求,上面的配置中

r = sub, obj, act

代表一個請求有三個标準元素:請求主體,請求對象,請求操作。

Policy definition: 代表政策,表示具體的權限定義的規則是什麼,上面配置中

p = sub, obj, act

Policy effect: Effect 用來判斷如果一個請求滿足了規則,是否需要同意請求

Matchers: 有請求,有規則,那麼請求是否比對某個規則,則是matcher進行判斷的

ACL with superuser 栗子

model的配置:

[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 || r.sub == "root"
           

Policey 配置:

p, alice, data1, read
p, bob, data2, write
           

當我們請求:alice,data1,read

根據比對規則,比對的結果就是true

當我們請求:alice,data1,write

根據比對規則,比對的規則就是false

RESful (KeyMatch2) 栗子

[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 && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
           

Policy 定義:

p, alice, /alice_data/:resource, GET
p, alice, /alice_data2/:id/using/:resId, GET
           

當我們請求

alice, /alice_data/hello, GET

根據matchers規則比對了

p, alice, /alice_data/:resource, GET

是以傳回true

alice, /alice_data/hello, POST

根據matchers規則沒有比對到,是以傳回false

RBAC 栗子

[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
           

在這裡引入了

role_definition

角色定義, g 用于判斷哪個使用者是否屬于哪個角色

Policy 配置:

p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write

g, alice, data2_admin
           

alice, data2, read

根據matchers 比對了alice 是data2_admin角色,并且

r.obj == p.obj && r.act == p.act

在gin-admin項目中的使用

這裡先梳理一下作者代碼中對casbin的使用,因為之前看了casbin在其他幾個項目中的使用,感覺都是有點亂,在在gin-admin這個項目的時候一開始也是感覺有點懵 ,沒有了解怎麼用,不過當把代碼梳理清楚之後,感覺gin-admin作者的使用還是非常好的。

gin-admin項目中關于casbin的使用分為

  1. 定義CasbinAdapter
  2. 初始化casbin
  3. 異步加載casbin權限

定義了CasbinAdapter

作者在gin-admin/internal/module/adapter/casbin.go 中定義了CasbinAdapter:

// CasbinAdapter casbin擴充卡
type CasbinAdapter struct {
	RoleModel         model.IRole
	RoleMenuModel     model.IRoleMenu
	MenuResourceModel model.IMenuActionResource
	UserModel         model.IUser
	UserRoleModel     model.IUserRole
}
           

這裡的CasbinAdapter是實作了

casbin

中的

Adapter

接口,即CasbinAdapter實作了LoadPolicy,SavePolicy,AddPolicy,RemovePolicy方法,并且作者通過在LoadPolicy将使用者權限和角色權限從資料庫中進行加載。

初始化

權限的初始化是通過下面代碼:

func InitCasbin(adapter persist.Adapter) (*casbin.SyncedEnforcer, func(), error) {
	cfg := config.C.Casbin
	if cfg.Model == "" {
		return new(casbin.SyncedEnforcer), nil, nil
	}

	e, err := casbin.NewSyncedEnforcer(cfg.Model)
	if err != nil {
		return nil, nil, err
	}
	e.EnableLog(cfg.Debug)
	err = e.InitWithModelAndAdapter(e.GetModel(), adapter)
	if err != nil {
		return nil, nil, err
	}
	e.EnableEnforce(cfg.Enable)

	cleanFunc := func() {}
	if cfg.AutoLoad {
		e.StartAutoLoadPolicy(time.Duration(cfg.AutoLoadInternal) * time.Second)
		cleanFunc = func() {
			e.StopAutoLoadPolicy()
		}
	}

	return e, cleanFunc, nil
}
           

這個部分主要是當我們通過頁面進行權限的配置後,我們需要将權限重新進行加載,這部分代碼在gin-admin/internal/app/bll/impl/bll/b_casbin.go中:

var chCasbinPolicy chan *chCasbinPolicyItem

type chCasbinPolicyItem struct {
	ctx context.Context
	e   *casbin.SyncedEnforcer
}

func init() {
	chCasbinPolicy = make(chan *chCasbinPolicyItem, 1)
	go func() {
		for item := range chCasbinPolicy {
			err := item.e.LoadPolicy()
			if err != nil {
				logger.Errorf(item.ctx, "The load casbin policy error: %s", err.Error())
			}
		}
	}()
}

// LoadCasbinPolicy 異步加載casbin權限政策
func LoadCasbinPolicy(ctx context.Context, e *casbin.SyncedEnforcer) {
	if !config.C.Casbin.Enable {
		return
	}

	if len(chCasbinPolicy) > 0 {
		logger.Infof(ctx, "The load casbin policy is already in the wait queue")
		return
	}

	chCasbinPolicy <- &chCasbinPolicyItem{
		ctx: ctx,
		e:   e,
	}
}
           

而在我們的更改權限的接口中都會通過調用LoadCasbinPolicy将權限政策進行加載。

總結

關于這個項目整理了三篇文章,也學習到了很多東西,其實到這篇文章,作者整體代碼自己已經樹立清楚了,很多人會覺得作者的項目目錄過于複雜,還有一些重複代碼,在你剛開始梳理代碼邏輯的時候還會感到一臉懵,但是當你耐心梳理完之後,你會發現,這樣寫原來會有這樣或者那樣的好處。作者剩餘的代碼就是關于web接口中的邏輯了,就不在做整理。

後面的計劃是通過這次對這次代碼的學習,寫一個blog的web項目。同時也會找下一個開源項目代碼進行學習

延伸閱讀

  • https://casbin.org/zh-CN/editor
  • https://casbin.org/docs/zh-CN/overview
  • https://www.cnblogs.com/yjf512/p/12200206.html

所有的努力都值得期許,每一份夢想都應該灌溉!