您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么在Go中使用Casbin進(jìn)行訪問控制”的相關(guān)知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強(qiáng),希望這篇“怎么在Go中使用Casbin進(jìn)行訪問控制”文章能幫助大家解決問題。
Casbin是一個強(qiáng)大的、高效的開源訪問控制框架,其權(quán)限管理機(jī)制支持多種訪問控制模型,Casbin只負(fù)責(zé)訪問控制。
其功能有:
支持自定義請求的格式,默認(rèn)的請求格式為{subject, object, action}
。
具有訪問控制模型model和策略policy兩個核心概念。
支持RBAC中的多層角色繼承,不止主體可以有角色,資源也可以具有角色。
支持內(nèi)置的超級用戶 例如:root
或administrator
。超級用戶可以執(zhí)行任何操作而無需顯式的權(quán)限聲明。
支持多種內(nèi)置的操作符,如 keyMatch
,方便對路徑式的資源進(jìn)行管理,如 /foo/bar
可以映射到 /foo*
在 Casbin 中, 訪問控制模型被抽象為基于 **PERM **
(Policy, Effect, Request, Matcher) [策略,效果,請求,匹配器]的一個文件。
Policy:定義權(quán)限的規(guī)則
Effect:定義組合了多個Policy之后的結(jié)果
Request:訪問請求
Matcher:判斷Request是否滿足Policy
首先會定義一堆Policy,讓后通過Matcher來判斷Request和Policy是否匹配,然后通過Effect來判斷匹配結(jié)果是Allow還是Deny。
Model是Casbin的具體訪問模型,其主要以文件的形式出現(xiàn),該文件常常以.conf
最為后綴。
Model CONF 至少應(yīng)包含四個部分: [request_definition]
, [policy_definition]
, [policy_effect]
, [matchers]
。
如果 model 使用 RBAC, 還需要添加[role_definition]
部分。
Model CONF 文件可以包含注釋。注釋以 # 開頭, # 會注釋該行剩余部分。
比如:
# Request定義 [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
request_definition:用于request的定義,它明確了e.Enforce(...)函數(shù)中參數(shù)的定義,sub, obj, act
表示經(jīng)典三元組: 訪問實體 (Subject),訪問資源 (Object) 和訪問方法 (Action)。
policy_definition:用于policy的定義,每條規(guī)則通常以形如p
的policy type
開頭,比如p,joker,data1,read
就是一條joker具有data1讀權(quán)限的規(guī)則。
role_definition:是RBAC角色繼承關(guān)系的定義。g
是一個 RBAC系統(tǒng),_, _
表示角色繼承關(guān)系的前項和后項,即前項繼承后項角色的權(quán)限。
policy_effect:是對policy生效范圍的定義,它對request的決策結(jié)果進(jìn)行統(tǒng)一的決策,比如e = some(where (p.eft == allow))
就表示如果存在任意一個決策結(jié)果為allow
的匹配規(guī)則,則最終決策結(jié)果為allow
。p.eft
表示策略規(guī)則的決策結(jié)果,可以為allow
或者deny
,當(dāng)不指定規(guī)則的決策結(jié)果時,取默認(rèn)值allow
。
matchers:定義了策略匹配者。匹配者是一組表達(dá)式,它定義了如何根據(jù)請求來匹配策略規(guī)則
Policy主要表示訪問控制關(guān)于角色、資源、行為的具體映射關(guān)系。
比如:
p, alice, data1, read p, bob, data2, write p, data2_admin, data2, read p, data2_admin, data2, write g, alice, data2_admin
它的關(guān)系規(guī)則很簡單,主要是選擇什么方式來存儲規(guī)則,目前官方提供csv文件存儲
和通過adapter適配器
從其他存儲系統(tǒng)中加載配置文件,比如MySQL, PostgreSQL, SQL Server, SQLite3,MongoDB,Redis,Cassandra DB等。
首先創(chuàng)建一個項目,叫casbin_test。
項目里的目錄結(jié)構(gòu)如下:
├─configs # 配置文件 ├─global # 全局變量 ├─internal # 內(nèi)部模塊 │ ├─dao # 數(shù)據(jù)處理模塊 │ ├─middleware # 中間件 │ ├─model # 模型層 │ ├─router # 路由 │ │ └─api │ │ └─v1 # 視圖 │ └─service # 業(yè)務(wù)邏輯層 └─pkg # 內(nèi)部模塊包 ├─app # 應(yīng)用包 ├─errcode # 錯誤代碼包 └─setting # 配置包
下載依賴包,如下:
go get -u github.com/gin-gonic/gin # Go語言casbin的依賴包 go get github.com/casbin/casbin # gorm 適配器依賴包 go get github.com/casbin/gorm-adapter # mysql驅(qū)動依賴 go get github.com/go-sql-driver/mysql # gorm 包 go get github.com/jinzhu/gorm
創(chuàng)建數(shù)據(jù)庫,如下:
CREATE DATABASE `casbin_test` DEFAULT CHARACTER SET utf8; GRANT Alter, Alter Routine, Create, Create Routine, Create Temporary Tables, Create View, Delete, Drop, Event, Execute, Index, Insert, Lock Tables, References, Select, Show View, Trigger, Update ON `casbin\_test`.* TO `ops`@`%`; FLUSH PRIVILEGES; DROP TABLE IF EXIST `casbin_rule`; CREATE TABLE `casbin_rule` ( `p_type` varchar(100) DEFAULT NULL COMMENT '規(guī)則類型', `v0` varchar(100) DEFAULT NULL COMMENT '角色I(xiàn)D', `v1` varchar(100) DEFAULT NULL COMMENT 'api路徑', `v2` varchar(100) DEFAULT NULL COMMENT 'api訪問方法', `v3` varchar(100) DEFAULT NULL, `v4` varchar(100) DEFAULT NULL, `v5` varchar(100) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='權(quán)限規(guī)則表'; /*插入操作casbin api的權(quán)限規(guī)則*/ INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`) VALUES ('p', 'admin', '/api/v1/casbin', 'POST'); INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`) VALUES ('p', 'admin', '/api/v1/casbin/list', 'GET');
由于代碼比較多,這里就不貼全部代碼了,全部代碼已經(jīng)放在gitee倉庫,可以自行閱讀,這些僅僅貼部分關(guān)鍵代碼。
(1)首先在configs
目錄下創(chuàng)建rbac_model.conf
文件,寫入如下代碼:
[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 = r.sub == p.sub && ParamsMatch(r.obj,p.obj) && r.act == p.act
(2)在internal/model
目錄下,創(chuàng)建casbin.go
文件,寫入如下代碼:
type CasbinModel struct { PType string `json:"p_type" gorm:"column:p_type" description:"策略類型"` RoleId string `json:"role_id" gorm:"column:v0" description:"角色I(xiàn)D"` Path string `json:"path" gorm:"column:v1" description:"api路徑"` Method string `json:"method" gorm:"column:v2" description:"訪問方法"` } func (c *CasbinModel) TableName() string { return "casbin_rule" } func (c *CasbinModel) Create(db *gorm.DB) error { e := Casbin() if success := e.AddPolicy(c.RoleId,c.Path,c.Method); success == false { return errors.New("存在相同的API,添加失敗") } return nil } func (c *CasbinModel) Update(db *gorm.DB, values interface{}) error { if err := db.Model(c).Where("v1 = ? AND v2 = ?", c.Path, c.Method).Update(values).Error; err != nil { return err } return nil } func (c *CasbinModel) List(db *gorm.DB) [][]string { e := Casbin() policy := e.GetFilteredPolicy(0, c.RoleId) return policy } //@function: Casbin //@description: 持久化到數(shù)據(jù)庫 引入自定義規(guī)則 //@return: *casbin.Enforcer func Casbin() *casbin.Enforcer { s := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s&parseTime=%t&loc=Local", global.DatabaseSetting.Username, global.DatabaseSetting.Password, global.DatabaseSetting.Host, global.DatabaseSetting.DBName, global.DatabaseSetting.Charset, global.DatabaseSetting.ParseTime, ) db, _ := gorm.Open(global.DatabaseSetting.DBType, s) adapter := gormadapter.NewAdapterByDB(db) enforcer := casbin.NewEnforcer(global.CasbinSetting.ModelPath, adapter) enforcer.AddFunction("ParamsMatch", ParamsMatchFunc) _ = enforcer.LoadPolicy() return enforcer } //@function: ParamsMatch //@description: 自定義規(guī)則函數(shù) //@param: fullNameKey1 string, key2 string //@return: bool func ParamsMatch(fullNameKey1 string, key2 string) bool { key1 := strings.Split(fullNameKey1, "?")[0] // 剝離路徑后再使用casbin的keyMatch3 return util.KeyMatch3(key1, key2) } //@function: ParamsMatchFunc //@description: 自定義規(guī)則函數(shù) //@param: args ...interface{} //@return: interface{}, error func ParamsMatchFunc(args ...interface{}) (interface{}, error) { name1 := args[0].(string) name2 := args[1].(string) return ParamsMatch(name1, name2), nil }
(3)在internal/dao
目錄下創(chuàng)建casbin.go
,寫入如下代碼:
func (d *Dao) CasbinCreate(roleId string, path, method string) error { cm := model.CasbinModel{ PType: "p", RoleId: roleId, Path: path, Method: method, } return cm.Create(d.engine) } func (d *Dao) CasbinList(roleID string) [][]string { cm := model.CasbinModel{RoleId: roleID} return cm.List(d.engine) }
(4)在internal/service
目錄下創(chuàng)建service.go
,寫入如下代碼:
type CasbinInfo struct { Path string `json:"path" form:"path"` Method string `json:"method" form:"method"` } type CasbinCreateRequest struct { RoleId string `json:"role_id" form:"role_id" description:"角色I(xiàn)D"` CasbinInfos []CasbinInfo `json:"casbin_infos" description:"權(quán)限模型列表"` } type CasbinListResponse struct { List []CasbinInfo `json:"list" form:"list"` } type CasbinListRequest struct { RoleID string `json:"role_id" form:"role_id"` } func (s Service) CasbinCreate(param *CasbinCreateRequest) error { for _, v := range param.CasbinInfos { err := s.dao.CasbinCreate(param.RoleId, v.Path, v.Method) if err != nil { return err } } return nil } func (s Service) CasbinList(param *CasbinListRequest) [][]string { return s.dao.CasbinList(param.RoleID) }
(5)在internal/router/api/v1
目錄下創(chuàng)建casbin.go
,寫入如下代碼:
type Casbin struct { } func NewCasbin() Casbin { return Casbin{} } // Create godoc // @Summary 新增權(quán)限 // @Description 新增權(quán)限 // @Tags 權(quán)限管理 // @Produce json // @Security ApiKeyAuth // @Param body body service.CasbinCreateRequest true "body" // @Success 200 {object} string "成功" // @Failure 400 {object} errcode.Error "請求錯誤" // @Failure 500 {object} errcode.Error "內(nèi)部錯誤" // @Router /api/v1/casbin [post] func (c Casbin) Create(ctx *gin.Context) { param := service.CasbinCreateRequest{} response := app.NewResponse(ctx) valid, errors := app.BindAndValid(ctx, ¶m) if !valid { log.Printf("app.BindAndValid errs: %v", errors) errRsp := errcode.InvalidParams.WithDetails(errors.Errors()...) response.ToErrorResponse(errRsp) return } // 進(jìn)行插入操作 svc := service.NewService(ctx) err := svc.CasbinCreate(¶m) if err != nil { log.Printf("svc.CasbinCreate err: %v", err) response.ToErrorResponse(errcode.ErrorCasbinCreateFail) } response.ToResponse(gin.H{}) return } // List godoc // @Summary 獲取權(quán)限列表 // @Produce json // @Tags 權(quán)限管理 // @Security ApiKeyAuth // @Param data body service.CasbinListRequest true "角色I(xiàn)D" // @Success 200 {object} service.CasbinListResponse "成功" // @Failure 400 {object} errcode.Error "請求錯誤" // @Failure 500 {object} errcode.Error "內(nèi)部錯誤" // @Router /api/v1/casbin/list [post] func (c Casbin) List(ctx *gin.Context) { param := service.CasbinListRequest{} response := app.NewResponse(ctx) valid, errors := app.BindAndValid(ctx, ¶m) if !valid { log.Printf("app.BindAndValid errs: %v", errors) errRsp := errcode.InvalidParams.WithDetails(errors.Errors()...) response.ToErrorResponse(errRsp) return } // 業(yè)務(wù)邏輯處理 svc := service.NewService(ctx) casbins := svc.CasbinList(¶m) var respList []service.CasbinInfo for _, host := range casbins { respList = append(respList, service.CasbinInfo{ Path: host[1], Method: host[2], }) } response.ToResponseList(respList, 0) return }
再在該目錄下創(chuàng)建一個test.go
文件,用于測試,代碼如下:
type Test struct { } func NewTest() Test { return Test{} } func (t Test) Get(ctx *gin.Context) { log.Println("Hello 接收到GET請求..") response := app.NewResponse(ctx) response.ToResponse("接收GET請求成功") }
(6)在internal/middleware
目錄下創(chuàng)建casbin_handler.go
,寫入如下代碼:
func CasbinHandler() gin.HandlerFunc { return func(ctx *gin.Context) { response := app.NewResponse(ctx) // 獲取請求的URI obj := ctx.Request.URL.RequestURI() // 獲取請求方法 act := ctx.Request.Method // 獲取用戶的角色 sub := "admin" e := model.Casbin() fmt.Println(obj, act, sub) // 判斷策略中是否存在 success := e.Enforce(sub, obj, act) if success { log.Println("恭喜您,權(quán)限驗證通過") ctx.Next() } else { log.Printf("e.Enforce err: %s", "很遺憾,權(quán)限驗證沒有通過") response.ToErrorResponse(errcode.UnauthorizedAuthFail) ctx.Abort() return } } }
(7)在internal/router
目錄下創(chuàng)建router.go
,定義路由,代碼如下:
func NewRouter() *gin.Engine { r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) casbin := v1.NewCasbin() test := v1.NewTest() apiv1 := r.Group("/api/v1") apiv1.Use(middleware.CasbinHandler()) { // 測試路由 apiv1.GET("/hello", test.Get) // 權(quán)限策略管理 apiv1.POST("/casbin", casbin.Create) apiv1.POST("/casbin/list", casbin.List) } return r }
最后就啟動項目進(jìn)行測試。
(1)首先訪問測試路徑,當(dāng)前情況下沒在權(quán)限表里,如下:
(2)將測試路徑添加到權(quán)限列表,如下:
(3)然后再次訪問測試路徑,如下:
并且從日志上也可以看到,如下:
關(guān)于“怎么在Go中使用Casbin進(jìn)行訪問控制”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。