Casbin 是一個強大的,開源的訪問控制框架,權限管理機制支持多種訪問控制模型; 並且支持多種編程語言;
文檔地址:https://casbin.org/docs/zh-CN/overview
Gin Golang 的 Web 框架,短小精悍
文檔地址: https://gin-gonic.com/docs/
Gorm Golang 的 ORM 框架
文檔地址:http://gorm.book.jasperxu.com/
今天我們要學習的是如何通過Casbin 來控制開發的 API 訪問權限
##### Casbin 工作原理
訪問控制模型被抽象為PERM(Policy,Effect,Request,Matcher) 的一個文件,如果切換項目的授權機制只用修改文件即可;
Policy: 定義權限的規則
Effect: 定義組合多個Policy 后是允許還是拒絕(allow/deny)
Request: 訪問的請求,可以理解為誰想訪問什么資源
Matcher:判斷Request 是否滿足Policy ,返回true或false
##### 在使用Casbin 控制后台接口時使用以下模型
[request_definition] r = sub, obj, act # 請求的規則
# r 是規則的名稱,sub 為請求的實體,obj 為資源的名稱, act 為請求的實際操作動作 [policy_definition] p = sub, obj, act # 策略的規則
# 同請求 [role_definition] g = _, _ # 角色的定義
# g 角色的名稱,第一個位置為用戶,第二個位置為角色,第三個位置為域(在多租戶場景下使用) [policy_effect] e = some(where (p.eft == allow)) # 任意一條 policy rule 滿足, 則最終結果為 allow [matchers] m = g(r.sub, p.sub) == true \ && keyMatch2(r.obj, p.obj) == true \ && regexMatch(r.act, p.act) == true \ || r.sub == "root"
# 前三個用來匹配上面定義的請求的規則, 最后一個或條件為:如果實體是root 直接通過, 不驗證權限
在理解了Casbin 的工作原理后,實際寫代碼測試一下
需要使用的外部包
go get github.com/casbin/casbin Casbin 官方庫
go get github.com/casbin/gorm-adapter Casbin 插件,用來將規則和策略保存到數據庫中
go get github.com/gin-gonic/gin Go Web 框架
go get github.com/go-sql-driver/mysql Go MySQL 驅動
go get github.com/jinzhu/gorm Go ORMpackage mainimport ( "fmt" "github.com/casbin/casbin"
"github.com/casbin/gorm-adapter" "github.com/gin-gonic/gin" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" )
// 統一響應結構體 type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } var O *gorm.DB var PO *gormadapter.Adapter var Enforcer *casbin.Enforcer
func ping(c *gin.Context) { var response Response response.Code = 0 response.Message = "success" response.Data = "" c.JSON(200, response) return } // 數據庫連接及角色規則的初始化 func connect() { dsn := "xxx:xxx@(xxxx:3xx6)/goapp?charset=utf8&parseTime=True&loc=Local" var err error O, err = gorm.Open("mysql", dsn) if err != nil { fmt.Println("connect DB error") panic(err) }
// 將數據庫連接同步給插件, 插件用來操作數據庫 PO = gormadapter.NewAdapterByDB(O)
// 這里也可以使用原生字符串方式
// Enforcer = casbin.NewEnforcer("./auth_model.conf", PO)
// 開啟權限認證日志 Enforcer.EnableLog(true)
// 加載數據庫中的策略 err = Enforcer.LoadPolicy() if err != nil { fmt.Println("loadPolicy error") panic(err) } // 創建一個角色,並賦於權限
// admin 這個角色可以訪問GET 方式訪問 /api/v2/ping res := Enforcer.AddPolicy("admin","/api/v2/ping","GET") if !res { fmt.Println("policy is exist") } else { fmt.Println("policy is not exist, adding") } // 將 test 用戶加入一個角色中 Enforcer.AddRoleForUser("test","root")
Enforcer.AddRoleForUser("tom","admin") // 請看規則中如果用戶名為 root 則不受限制 } func main() { defer O.Close() connect() g := gin.Default()
// 這里的接口沒有使用權限認證中間件 version1 := g.Group("/api/v1") { version1.GET("/ping", ping) // 這個是通用的接口 }
// 接口使用權限認證中間件 version2 := g.Group("/api/v2", CasbinMiddleWare) { version2.GET("/ping", ping) } _ = g.Run(":8099") } // casbin middleware 權限認證中間件 func CasbinMiddleWare(c *gin.Context) { var userName string userName = c.GetHeader("userName") if userName == "" { fmt.Println("headers invalid") c.JSON(200, gin.H{ "code": 401, "message": "Unauthorized", "data": "", }) c.Abort() return } // 請求的path p := c.Request.URL.Path // 請求的方法 m := c.Request.Method
// 這里認證 res,err := Enforcer.EnforceSafe(userName,p,m) // 這個 HasPermissionForUser 跟上面的有什么區別
// EnforceSafe 會驗證角色的相關的權限
// 而 HasPermissionForUser 只驗證用戶是否有權限 //res = Enforcer.HasPermissionForUser(userName,p,m) if err != nil { fmt.Println("no permission ") fmt.Println(err) c.JSON(200, gin.H{ "code": 401, "message": "Unauthorized", "data": "", }) c.Abort() return } if !res { fmt.Println("permission check failed") c.JSON(200, gin.H{ "code": 401, "message": "Unauthorized", "data": "", }) c.Abort() return } c.Next() }
程序運行后數據庫數據為
p 代表的是策略 admin 角色可以使用GET 訪問 /api/v2/ping
g 代表的是角色 test 用戶在root 角色中
tom 在admin 角色中
所以在測試時請求頭
userName = root 有正常響應 (這里不會到數據庫驗證,策略最后一條)
userName = tom 正常響應 (tom 有admin 角色 , 所以驗證通過)
userName = role_admin 正常響應 (參考這里:https://casbin.org/docs/zh-CN/rbac , 正常情況下用戶名和角色名稱不應該一樣)
userName = *** 都無法通過認證
########
最后, 一般的后台開發不會這樣設計, 后面繼續學習,理解 Casbin 工作原理,為以后開發打下基礎;