前言
casbin是目前流行的身份鉴定工具之一,笔者在近期写的一个项目中也使用到了casbin对于项目的权限进行鉴定,于是在此分享一下笔者是如何使用casbin进行权限判定的。
注意:以下权限验证方式以golang gin gorm为例
安装
安装casbin直接以官网示例进行即可,在此给出官网网址:https://casbin.org/docs/get-started
如果需要将策略存储至数据库,则可以使用对应的Adapter,在此给出Adapter官网网址:https://casbin.org/docs/adapters
如笔者使用的是go+Gorm,选择对应的Adapter即可
模型选择
因为要进行权限验证以及动态修改用户权限,在此选择casbin的RBAC模型,具体配置文件如下
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.act, p.act) && keyMatch(r.obj, p.obj)
其中keyMatch()为官方提供的函数,具体匹配方法如下
func KeyMatch(key1 string, key2 string) bool {
i := strings.Index(key2, "*")
if i == -1 {
return key1 == key2
}
if len(key1) > i {
return key1[:i] == key2[:i]
}
return key1 == key2[:i]
}
初始化配置
初始化casbin可以参考官方示例:https://casbin.org/zh/docs/get-started
在此给出个人初始化方法
casbin.go
package auth
import (
"os"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
)
var Casbin *casbin.Enforcer
func InitCasbin() {
// os.Getenv("MYSQL_DSN")可以参考gorm官方示例:
//user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
a, err := gormadapter.NewAdapter("mysql", os.Getenv("MYSQL_DSN"), true)
if err != nil {
panic(err)
}
m, err := model.NewModelFromString(`
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = g(r.sub, p.sub) && keyMatch(r.act, p.act) && keyMatch(r.obj, p.obj)
`)
if err != nil {
panic(err)
}
e, err := casbin.NewEnforcer(m, a)
if err != nil {
panic(err)
}
Casbin = e
Casbin.LoadPolicy()
// 检查基础权限是否存在,请按需修改
if ok, _ := Casbin.Enforce("common_admin", "admin/user", "POST"); !ok {
initPolicy()
}
}
初始化基础策略,在此给出个人写法,可以自行调整
initPolicy
func initPolicy() {
// add base policies
// 注意,此处一定要填写allow或deny,否则会报错
Casbin.AddPolicies(
[][]string{
// 写入对应权限,请自行调整
// suspend user can't do anything
{model.StatusSuspendUser, "*", "*", "deny"},
// inactive user can't create
{model.StatusInactiveUser, "user*", "*", "allow"},
{model.StatusInactiveUser, "file*", "GET", "allow"},
{model.StatusInactiveUser, "file*", "DELETE", "allow"},
{model.StatusInactiveUser, "filefolder*", "GET", "allow"},
{model.StatusInactiveUser, "filefolder*", "DELETE", "allow"},
{model.StatusInactiveUser, "filestore*", "GET", "allow"},
{model.StatusInactiveUser, "share*", "GET", "allow"},
{model.StatusInactiveUser, "share*", "DELETE", "allow"},
// active user can create file, filefolder and share
{model.StatusActiveUser, "share*", "*", "allow"},
{model.StatusActiveUser, "file*", "*", "allow"},
{model.StatusActiveUser, "filefolder*", "*", "allow"},
{model.StatusActiveUser, "rank*", "GET", "allow"},
// admin user can change user status
{model.StatusAdmin, "admin/user*", "*", "allow"},
{model.StatusAdmin, "admin/login*", "*", "allow"},
// super admin can do anything
{model.StatusSuperAdmin, "*", "*", "allow"},
},
)
// add group policies
Casbin.AddGroupingPolicies(
[][]string{
{model.StatusActiveUser, model.StatusInactiveUser},
{model.StatusAdmin, model.StatusActiveUser},
},
)
Casbin.SavePolicy()
}
具体名称含义
const (
// super admin
StatusSuperAdmin = "super_admin"
// admin User
StatusAdmin = "common_admin"
// active User
StatusActiveUser = "active"
// inactive User
StatusInactiveUser = "inactive"
// Suspend User
StatusSuspendUser = "suspend"
)
后续直接在启动服务的时候进行初始化即可
func Init() {
//其他初始化
............
// start casbin
auth.InitCasbin()
}
中间件写法
casbin已经配置好了,接下来就是编写中间件进行权限验证,在此给出gin中间件写法
func CasbinAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取用户状态,此处已将状态写入gin.context
userStatus := c.MustGet("Status").(string)
// 获取用户申请的资源和方法
method := c.Request.Method
path := c.Request.URL.Path
//过滤默认头部,这里将/api/v1/ 过滤
object := strings.TrimPrefix(path, "/api/v1/")
// 使用casbin提供的函数进行权限验证
if ok, _ := auth.Casbin.Enforce(userStatus, object, method); !ok {
// 如果无权限则返回无权限
c.JSON(200, serializer.NotAuthErr("not auth"))
// 中断后续执行
c.Abort()
}
}
}
最后将中间件加入路由即可,在此给出个人使用gin框架写法
v1 := r.Group("/api/v1")
{
auth := v1.Group("")
auth.Use(middleware.CasbinAuth())
{
// 放入需要权限验证的api
}
}
一个需要注意的问题
Q:如果需要动态修改权限怎么办?
A:如果想要修改用户权限,直接修改存储的用户身份即可。
如果想要添加新的权限或删除权限请使用casbin的对应函数,在此给出go的官方api
// e.AddPolicy(...)
// e.RemovePolicy(...)
// Save the policy back to DB.
e.SavePolicy()
请不要在initPolicy进行修改,因为这样并不会将权限加入数据库。如果想要在initPolicy进行修改,请删除数据库中casbin自动创建的casbin_rule表。
结尾
如果有更多疑问,可以在评论区留言