前言
前面寫了一篇 【iris-go 框架構建登陸 API 專案開發過程】 的文章,因為篇幅的關係這篇文章並沒有介紹如何實現許可權控制。接下來我將會在這篇文章中介紹如何使用 casbin 在 iris 的專案中實現許可權控制,因為我的專案採用 grom 做資料處理,所以是 gorm + casbin。
本文純屬個人簡陋觀點和經驗,如果有錯誤的地方,歡迎大家指出。
這裡繼續講一些廢話,原本 IrisAdminApi 專案採用的最原始的 RABC 的許可權控制實現,分別建立 roles , permissions 表,然後定義關聯關係,寫一些關聯建立邏輯。在實現的過程發現不管是使用 gorm ,xorm,beego的orm,定義關聯關係都挺麻煩的,特別是多對對關係,還有一些多型關係等等。可能 Laravel 使用習慣性了,發現框架功能太齊全,對自身的提升真的會有阻礙。
這時候我就想有沒有別人實現好的許可權控制輪子,結果一頓搜尋,讓我找到了 casbin。這就是我和 casbin 的初識了。
剛見到 casbin 其實有些不知道如何下手,畢竟沒有見過類似的專案。相信很多新手也會有同樣的感受。不過沒關係,一回生兩回熟,像我們這樣經過九年義務教育的社會主義好青年最不怕的就是學習新事物。
大致介紹下我學習 casbin 的思路:
- 首先:看文件,就像買了新東西看說明書一樣,學習任何新事物必不可少的一步。不需要完全背下來,大致有個印象就好。不懂的地方可以多看幾遍,不要怕麻煩。
- 接下來:在文件中找到我們需要的功能,我們需要的是一個與 gorm 工作的 RBAC 許可權控制,正好我發現作者有寫 gorm 相關的介面卡,其實作者為不同語言寫非常多的介面卡,這樣可以省去我們的很多時間。
- 開啟 gorm-adapter 專案,按照說明執行安裝介面卡:
go get github.com/casbin/gorm-adapter
- 檢視例項程式碼,很多專案多有例項程式碼,其實這些程式碼的價值比文件的價值更高,能讓我們更好的理解專案。特別是註釋,需要仔細看完。
package main
import (
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v2"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 初始化一個 Gorm 介面卡並且在一個 Casbin enforcer 中使用它:
// 這個介面卡會使用一個名為 "casbin" 的 MySQL 資料庫。
// 如果資料庫不存在,介面卡會自動建立它。
// 你同樣也可以像這樣 gormadapter.NewAdapterByDB(gormInstance) 使用一個已經存在的 gorm 例項。
a := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") //你的驅動和資料來源
e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a)
// 或者你可以像這樣使用一個其他的資料庫 "abc" :
// 介面卡會使用名為 "casbin_rule" 的資料表。
// 如果資料表不存在,介面卡會自動建立它。
// a := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/abc", true)
// 從資料庫載入策略規則
e.LoadPolicy()
// 檢查許可權
e.Enforce("alice", "data1", "read")
// 更新策略
// e.AddPolicy(...)
// e.RemovePolicy(...)
// 儲存策略到資料庫
e.SavePolicy()
}
看完這個例項的程式碼和註釋,相信你已經瞭解 casbin 怎麼使用了。如果還不瞭解,那就多看幾遍。
在看文件的過程中我發現作者已經寫好了 RABC 相關的 API 。所以我們並不需要去重複的這些工作。你沒有看到?這說明了仔細看文件的重要的性。
現在我們接著前面的專案,將 casbin 功能新增到專案中。編輯 models/base.go 檔案:
var Db *gorm.DB var err error var dirverName string var conn string var Enforcer *casbin.Enforcer func Register() { dirverName = "mysql" if isTestEnv() { //如果是測試使用測試資料庫 conn = "root:wemT5ZNuo074i4FNsTwl4KhfVSvOlBcF@(127.0.0.1:3306)/tiris" } else { conn = "root:wemT5ZNuo074i4FNsTwl4KhfVSvOlBcF@(127.0.0.1:3306)/iris" } //初始化資料庫 Db, err = gorm.Open(dirverName, conn+"?charset=utf8&parseTime=True&loc=Local") if err != nil { color.Red(fmt.Sprintf("gorm open 錯誤: %v", err)) } a := gormadapter.NewAdapter("mysql",conn) Enforcer, _ = casbin.NewEnforcer("examples/rbac_model.conf", a) Enforcer.LoadPolicy() }
這裡我們不需要生成新的 casbin 資料庫,直接使用在 iris 資料庫生成 casbin_rule 資料表。同時將
rbac_model.conf
檔案複製到自己的專案的對應路徑中。這樣 casbin 就已經整合好了,重啟專案就會生成對應的 casbin_rule 資料表了。為什麼沒有使用 e.AddPolicy(…),e.SavePolicy(),e.Enforce(“alice”, “data1”, “read”) 這些方法? 如果你還有這樣的疑問,說明你沒有看例項的註釋,要麼就是沒有仔細閱讀。請多看幾遍。
雖然我們已經整合了 casbin ,但是我們還沒有實現許可權控制的目的。如果實現?我們需要關聯角色,許可權等資料到使用者,並且在使用者訪問相應資源的時候檢查使用者是否擁有許可權。
到這例有經驗的朋友就會發現,我們需要一箇中介軟體去實現使用者許可權的檢測。那麼我們移駕到 iris 的中介軟體文件,我們仔細的看以遍文件,你會發現已經有了 casbin 的中介軟體實現。並不需要我們執行編寫。
按照說明文件,先安裝 casbin 還有 casbin-middleware,分別執行一下命令:
go get github.com/casbin/casbin
go get github.com/iris-contrib/middleware/casbin
- 然後按照例項中的程式碼將中介軟體新增到路由:
package routers import ( "IrisAdmin/controllers" "IrisAdmin/middleware" "IrisAdmin/models" "github.com/kataras/iris/v12" "github.com/casbin/casbin/v2" cm "github.com/iris-contrib/middleware/casbin" ) func Register(app *iris.Application) { // 路由集使用跨域中介軟體 CrsAuth() // 允許 Options 方法 AllowMethods(iris.MethodOptions) main := app.Party("/", middleware.CrsAuth()).AllowMethods(iris.MethodOptions) { v1 := main.Party("/v1") { v1.Post("/admin/login", controllers.UserLogin) v1.PartyFunc("/admin", func(admin iris.Party) { casbinMiddleware := cm.New(models.Enforcer) admin.Use(middleware.JwtHandler().Serve, middleware.AuthToken,casbinMiddleware.ServeHTTP) //登入驗證 admin.Get("/logout", controllers.UserLogout).Name = "退出" }) } } }
- 這裡中介軟體有一個新的 `casbinmodel.conf`檔案,需要把裡面的內容替換到前面的 `rbac_model.conf` 檔案中。
- 現在當使用者登陸之後訪問任何介面都會出現 `403` 拒絕訪問的狀態返回,這說明中介軟體生效了。接下來我們需要使用 casbin 提供的 Api 將使用者,角色和許可權相互關聯起來。
- 開啟 [rbac-api](https://casbin.org/docs/zh-CN/rbac-api "rbac-api") 頁面,我們看到有一個 `AddRoleForUser()` 給使用者新增角色的方法。我們將它新增到使用者的建立和更新方法中,實現使用者和角色的關聯。
```go
func CreateUser() (user *User) {
salt, _ := bcrypt.Salt(10)
hash, _ := bcrypt.Hash("password", salt)
user = &User{
Username: "username",
Password: hash,
Name: "name",
}
if err := Db.Create(user).Error; err != nil {
color.Red(fmt.Sprintf("CreateUserErr:%s \n ", err))
}
userId := strconv.FormatUint(uint64(user.ID), 10)
if _, err := database.GetEnforcer().AddRoleForUser(userId, "1"); err != nil {
color.Red(fmt.Sprintf("CreateUserErr:%s \n ", err))
}
return
}
這裡需要注意的是
AddRoleForUser()
函式接受的引數都是字串,所以使用者和角色的 Id 都需要轉換一下格式。如果是多角色傳入多個角色的 Id 迴圈新增即可。接下來我們要關聯角色和許可權, 在 rbac-api 頁面,找了半天發現沒有新增許可權的方法。仔細看過文件多次之後,確定真的沒有新增許可權到角色的方法。那麼,我們接下來該怎麼辦呢?我的解決方法是檢視 casbin 的原始碼。因為我確定新增許可權的方法肯定是存在的,只是rbac-api 頁面上沒有而已。
果然,我在文件找到了相關方法:
// AddPolicy adds an authorization rule to the current policy. // If the rule already exists, the function returns false and the rule will not be added. // Otherwise the function returns true by adding the new rule. func (e *Enforcer) AddPolicy(params ...interface{}) (bool, error) { return e.AddNamedPolicy("p", params...) }
是不是很眼熟。沒錯,就是在前面介面卡例項中出現過的方法。它的說明是新增策略,當時沒想到策略就是許可權,真的是一葉障目,不見南山。眾裡尋他千百度…..
既然找到方法,那麼將角色關聯許可權(策略)的邏輯新增到角色的建立和更新當中。
roleId := strconv.FormatUint(uint64(role.ID), 10) var perms []Permission models.DB.Where("id in (?)", permIds).Find(&perms) for _, perm := range perms { if _, err := models.Enforcer.AddPolicy(roleId, perm.Name, perm.Act); err != nil { color.Red(fmt.Sprintf("AddPolicy:%s \n", err)) } }
同樣
AddPolicy()
函式引數都是字串,角色 Id 也需要轉換一下。所有資料都關聯好了之後,我們建立好相關的角色和許可權資料。使用者就可以正常訪問登陸了。詳細程式碼請檢視專案 IrisAdminApi 。
寫完了,感覺有點亂。主要還是講述我解決需求和問題的一個思路。希望對你有幫助。
還是那句話:能力有限,不喜勿噴。
本作品採用《CC 協議》,轉載必須註明作者和本文連結