本次测试代码是基于单租户的RBAC鉴权
依赖
github.com/casbin/casbin/v2
github.com/casbin/gorm-adapter/v2
文件存储规则文件
model.pml
[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
policy.csv
p, admin, /user, write
p, admin, /user, read
p, admin, /order, write
p, admin, /order, read
p, admin, /user, put
p, systemManager, /user/userlist, read
p, systemManager, /order/orderlist, read
g, demo, admin
g, demo1, admin
g, kongdy, admin
g, kongdy, systemManager
这里是基于单租户RBAC的鉴权模式
p 开头的为 policy
g 开头的为用户-角色
model.pml文件中matchers字段:请求 r 的 sub 与 g 的用户匹配,匹配到后,减少匹配到的角色中,是否有同时匹配 obj 和 act
可以嵌套角色,默认最多可嵌套10层,但需要注意用户与角色不要存在相同值,否则会拥有该重名角色的权限
p, userManager, /user, write
p, userManager, /user, read
p, userManager, /user, put
p, orderManager, /order, write
p, orderManager, /order, read
p, userManager, /user/userlist, read
p, orderManager, /order/orderlist, read
g, admin, userManager
g, admin, orderManager
g, manage, userManager
g, demo, admin
g, demo1, manage
这里可以理解为
demo用户拥有admin角色权限,admin角色继承userManager和orderManager角色,即同时拥有这两个角色的访问控制
demo1用户拥有userManager角色
也可以理解为
demo用户拥有admin角色权限,admin角色继承userManager和orderManager角色,即同时拥有这两个角色的访问控制
demo1用户拥有userManager角色
admin用户拥有userManager、orderManager两个角色
manage拥有userManager角色
理解以上policy后,也就明白了为什么用户名要与角色名称不要重叠的原因
main.go
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
)
func main() {
e, err := casbin.NewEnforcer("./model.pml", "./policy.csv")
if err != nil {
fmt.Println("出错啦", err)
return
}
//基本权限设置
CheckPermi(e, "demo", "/user", "read") //
CheckPermi(e, "demo", "/user", "put")
CheckPermi(e, "demo", "/user", "write")
CheckPermi(e, "demo", "/order", "read")
CheckPermi(e, "demo", "/order", "write")
CheckPermi(e, "demo", "/user/userlist", "read")
CheckPermi(e, "demo", "/user/userlist", "write")
CheckPermi(e, "demo", "/order/orderlist", "read")
CheckPermi(e, "demo", "/order/orderlist", "write")
CheckPermi(e, "demo1", "/user", "read")
CheckPermi(e, "demo1", "/user", "write")
CheckPermi(e, "demo1", "/user", "put")
CheckPermi(e, "demo1", "/order", "read")
CheckPermi(e, "demo1", "/order", "write")
CheckPermi(e, "demo1", "/user/userlist", "read")
CheckPermi(e, "demo1", "/user/userlist", "write")
CheckPermi(e, "demo1", "/order/orderlist", "read")
CheckPermi(e, "demo1", "/order/orderlist", "write")
CheckPermi(e, "admin", "/user", "read")
CheckPermi(e, "admin", "/user", "write")
CheckPermi(e, "admin", "/user", "put")
CheckPermi(e, "admin", "/order", "read")
CheckPermi(e, "admin", "/order", "write")
CheckPermi(e, "admin", "/user/userlist", "read")
CheckPermi(e, "admin", "/user/userlist", "write")
CheckPermi(e, "admin", "/order/orderlist", "read")
CheckPermi(e, "admin", "/order/orderlist", "write")
}
func CheckPermi(e *casbin.Enforcer, sub, obj, act string) {
ok, err := e.Enforce(sub, obj, act)
if err != nil {
return
}
if ok == true {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
常用操作
增加用户角色
e.AddRoleForUser("kongdy", "systemManager")
e.SavePolicy()
e.AddRolesForUser("demo1", []string{"systemManager", "admin"})
e.SavePolicy()
增加鉴权规则
e.AddPolicy("admin", "/user", "put")
e.SavePolicy()
删除角色(会将所有拥有该角色的记录均删除)
e.DeleteRole("systemManager")
e.SavePolicy()
基于gorm的权限控制
上面的示例基于文件存储来实现的,当数量上去后,管理起来非常不方便,下面采用数据库来存储数据
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v2" // 版本注意与casbin保持一致
_ "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 Db *gorm.DB
var PO *gormadapter.Adapter
var Enforcer *casbin.Enforcer
// 数据库连接及角色规则的初始化,会检查并自动创建 casbin_rule 表
func connect() {
dsn := "root:root@(127.0.0.1:3306)/gin_study?charset=utf8&parseTime=True&loc=Local"
var err error
Db, err = gorm.Open("mysql", dsn)
if err != nil {
fmt.Println("connect DB error")
panic(err)
}
// 将数据库连接同步给插件, 插件用来操作数据库
PO, _ = gormadapter.NewAdapterByDB(Db)
// 这里也可以使用原生字符串方式
//
Enforcer, _ = casbin.NewEnforcer("model.pml", PO)
// 开启权限认证日志
// Enforcer.EnableLog(true)
// 加载数据库中的策略
err = Enforcer.LoadPolicy()
if err != nil {
fmt.Println("loadPolicy error")
panic(err)
}
}
func main() {
connect()
//基本权限设置
CheckPermi(Enforcer, "demo", "/user", "read")
CheckPermi(Enforcer, "demo", "/user", "put")
CheckPermi(Enforcer, "demo", "/user", "write")
CheckPermi(Enforcer, "demo", "/order", "read")
CheckPermi(Enforcer, "demo", "/order", "write")
CheckPermi(Enforcer, "demo", "/user/userlist", "read")
CheckPermi(Enforcer, "demo", "/user/userlist", "write")
CheckPermi(Enforcer, "demo", "/order/orderlist", "read")
CheckPermi(Enforcer, "demo", "/order/orderlist", "write")
}
func CheckPermi(e *casbin.Enforcer, sub, obj, act string) {
res, err := e.Enforce(sub, obj, act)
if err != nil {
fmt.Printf("出错啦:e=%+v\n, err = %+v\n", e, err)
return
}
if res == true {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
中间件
// casbin middleware 权限认证中间件
func CasbinMiddleWare(enforcer *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) {
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.Enforce(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()
}
}