1. 程式人生 > >從別人的程式碼中學習golang系列--03

從別人的程式碼中學習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) 栗子 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 && 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 栗子 model的配置: ``` [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` 所以返回true ## 在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 } ``` ### 非同步載入casbin許可權 這個部分主要是當我們通過頁面進行許可權的配置後,我們需要將許可權重新進行載入,這部分程式碼在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/122002