如何在Go中使用Casbin進行訪問控制
Casbin是什么
Casbin是一個強大的、高效的開源訪問控制框架,其權(quán)限管理機制支持多種訪問控制模型,Casbin只負責訪問控制。
其功能有:
- 支持自定義請求的格式,默認的請求格式為
{subject, object, action}。 - 具有訪問控制模型model和策略policy兩個核心概念。
- 支持RBAC中的多層角色繼承,不止主體可以有角色,資源也可以具有角色。
- 支持內(nèi)置的超級用戶 例如:
root或administrator。超級用戶可以執(zhí)行任何操作而無需顯式的權(quán)限聲明。 - 支持多種內(nèi)置的操作符,如
keyMatch,方便對路徑式的資源進行管理,如/foo/bar可以映射到/foo*
Casbin的工作原理
在 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。
Casbin的核心概念
Model
Model是Casbin的具體訪問模型,其主要以文件的形式出現(xiàn),該文件常常以.conf最為后綴。
- Model CONF 至少應包含四個部分:
[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é)果進行統(tǒng)一的決策,比如
e = some(where (p.eft == allow))就表示如果存在任意一個決策結(jié)果為allow的匹配規(guī)則,則最終決策結(jié)果為allow。p.eft表示策略規(guī)則的決策結(jié)果,可以為allow或者deny,當不指定規(guī)則的決策結(jié)果時,取默認值allow。 - matchers:定義了策略匹配者。匹配者是一組表達式,它定義了如何根據(jù)請求來匹配策略規(guī)則
Policy
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)建項目
首先創(chuàng)建一個項目,叫casbin_test。
項目里的目錄結(jié)構(gòu)如下:
├─configs # 配置文件
├─global # 全局變量
├─internal # 內(nèi)部模塊
│ ├─dao # 數(shù)據(jù)處理模塊
│ ├─middleware # 中間件
│ ├─model # 模型層
│ ├─router # 路由
│ │ └─api
│ │ └─v1 # 視圖
│ └─service # 業(yè)務邏輯層
└─pkg # 內(nèi)部模塊包
├─app # 應用包
├─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 '角色ID',
`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');代碼開發(fā)
由于代碼比較多,這里就不貼全部代碼了,全部代碼已經(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:"角色ID"`
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的keyMatch2
return util.KeyMatch2(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:"角色ID"`
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
}
// 進行插入操作
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 "角色ID"
// @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è)務邏輯處理
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
}最后就啟動項目進行測試。
驗證
(1)首先訪問測試路徑,當前情況下沒在權(quán)限表里,如下:

(2)將測試路徑添加到權(quán)限列表,如下:

(3)然后再次訪問測試路徑,如下:

并且從日志上也可以看到,如下:

到此這篇關(guān)于如何在Go中使用Casbin進行訪問控制的文章就介紹到這了,更多相關(guān)Go Casbin訪問控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang中使用Date進行日期格式化(沿用Java風格)
這篇文章主要介紹了Golang中使用Date進行日期格式化,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04

