Casbin + Gin + Gorm 学习探索
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 工作原理,为以后开发打下基础;