Beego使用Casbin进行权限管理(MySQL)入门

前言

CasbinCasbinCasbin

名词理解

/user/1GETPOST

1.安装

Casbinbeego-ORM-Adapter
go get github.com/casbin/casbin
go get github.com/casbin/beego-orm-adapter
beego-ORM-Adapter

2.配置

confcasbin.conf

conf/casbin.conf

[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) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)

然后假设我们Beego已经注册好MySQL链接,如下所示:

err := orm.RegisterDataBase("default", "mysql", dbLink, maxIdle, maxConn)
beego-ORM-Adapter

3.Casbin数据库模型

Casbin

models/Casbin.go

type CasbinRule struct {
	Id    int       // 自增主键
	PType string    // Policy Type - 用于区分 policy和 group(role)
	V0    string    // subject
	V1    string    // object
	V2    string    // action
	V3    string    // 这个和下面的字段无用,仅预留位置,如果你的不是
	V4    string    // sub, obj, act的话才会用到
	V5    string    // 如 sub, obj, act, suf就会用到 V3
}

然后直接通过orm注册模型,并migrate到数据库中,这个和其他模型一样

models/Casbin.go

func init(){
    orm.RegisterModel(new(CasbinRule))
    _ = orm.RunSyncdb("default", false, false)
}

4.Adapter

Adapter
beego-ORM-AdapterNewAdapter
func NewAdapter(driverName string, dataSourceName string, dbSpecified ...bool) *Adapter {
	a := &Adapter{}
	a.driverName = driverName
	a.dataSourceName = dataSourceName

	.....

	// Open the DB, create it if not existed.
	// 打开一个Database链接,如果不存在
	a.open()

	.....

	return a
}
func (a *Adapter) open() {
	.....

	err = a.registerDataBase("default", a.driverName, a.dataSourceName)
	if err != nil {
		panic(err)
	}

	.....

	a.o = orm.NewOrm()
	a.o.Using("casbin")

	a.createTable()
}
NewAdapteropenopenbeego-ORM-AdapterAdapterorm.OrmerAdapter

/models/Casbin.go

// 注意,这个Enforcer很重要,Casbin使用都是调用这个变量
var Enforcer *casbin.Enforcer

type Adapter struct {
	o orm.Ormer
}
func RegisterCasbin() {
	a := &Adapter{}
	a.o = orm.NewOrm()
	// 这个我不知道干嘛的
	runtime.SetFinalizer(a, finalizer)
	// Enforcer初始化 - 即传入Adapter对象
	Enforcer = casbin.NewEnforcer("conf/casbin.conf", a)
	// Enforcer读取Policy
	err := Enforcer.LoadPolicy()
	if err != nil {
		panic(err)
	}
}
// finalizer is the destructor for Adapter.
// 这个函数里面啥都没有,就是这样
func finalizer(_ *Adapter) {}
beego-ORM-Adapter

/models/Casbin.go

// 注意,方法对应的具体代码要从beego-ORM-Adapter/adapter.go中复制过来
// 这里方法里面使用的orm操作还是要根据自己的
// 实际情况作出调整,不要盲目复制
func loadPolicyLine(line CasbinRule, model model.Model){}
func savePolicyLine(ptype string, rule []string) CasbinRule{}

func (a *Adapter) LoadPolicy(model model.Model) error{}
func (a *Adapter) SavePolicy(model model.Model) error{}
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error{}
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error{}
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error{}
savePolicyLinea.dropTable()a.createTable()
func (a *Adapter) SavePolicy(model model.Model) error {
	var lines []CasbinRule
	for ptype, ast := range model["p"] {
		for _, rule := range ast.Policy {
			line := savePolicyLine(ptype, rule)
			lines = append(lines, line)
		}
	}
	for ptype, ast := range model["g"] {
		for _, rule := range ast.Policy {
			line := savePolicyLine(ptype, rule)
			lines = append(lines, line)
		}
	}
	_, err := a.o.InsertMulti(len(lines), lines)
	return err
}
RegisterCasbin()

/models/Casbin.go

func init(){
    orm.RegisterModel(new(CasbinRule))
    // 实际上同步数据库在整个Beego项目中只需要执行一次,如果
    // 您在别的地方已经同步数据库,这里就不用在执行一次 RunSyncdb
    _ = orm.RunSyncdb("default", false, false)
    // 初始化 Casbin
    RegisterCasbin()
}
Enforce

5.Role角色模型

我采用角色控制访问Restful Api的方法,每个用户都有自己的角色,这是一对多(OneToMany)的关系。直接定义一个角色模型如下:

models/Role.go

type Role struct {
	Id    int     `orm:"auto;pk" description:"角色序号" json:"role_id"`
	Name  string  `orm:"unique"  description:"角色名"   json:"role_name"`
	Users []*User `orm:"reverse(many)" description:"用户列表" json:"users"`
}
func init(){
    orm.RegisterModel(new(Role))
    // 这里因为在别的地方已经同步过数据库了,就不同步了
}

然后我们还得初始化最基本的三个角色,分别是管理员、用户、匿名(未登陆的用户都是匿名),这个根据你项目实际需求去初始化。

models/Role.go

var (
	RoleAdmin      = "admin"
	RoleUser       = "user"
	RoleAnonymous  = "anonymous"
	RolesId        = map[string]int{
		RoleAdmin:      -1,
		RoleUser:       -1,
		RoleAnonymous:  -1,
	}
)
// 注册角色模型 - 初始化
func RegisterRoles() {
	o := orm.NewOrm()
	// 这里我通过遍历上面构建的一个字典来写入数据库
	// 如果不愿意使用骚操作的话,直接写三个ReadOrCreate就好了
	// GetRoleString方法是必须的
	for key, _ := range RolesId {
		_, id, err := o.ReadOrCreate(&Role{Name: GetRoleString(key)}, "Name")
		if err != nil {
			panic(err)
		}
		RolesId[key] = int(id)
	}
}
// 这个方法主要用于在Name字段加个前缀role_
func GetRoleString(s string) string {
	if strings.HasPrefix(s, "role_") {
		return s
	}
	return fmt.Sprintf("role_%s", s)
}
Namerole_Casbing(role)adminuserNamerole_
Casbin

models/Role.go

// 向Casbin添加角色继承策略规则
func AddRolesGroupPolicy() {
	// 普通管理员继承用户
	_ = Enforcer.AddGroupingPolicy(GetRoleString(RoleAdmin), GetRoleString(RoleUser))
	// 用户继承匿名者
	_ = Enforcer.AddGroupingPolicy(GetRoleString(RoleUser), GetRoleString(RoleAnonymous))
}

最后在初始化包的时候调用这两个方法:

func init(){
    RegisterRoles()
    AddRolesGroupPolicy()
}

6.User模型

User用户名Username密码Password角色Role

models/User.go

type User struct {
	// 用户模型
	Id          int        `orm:"auto;pk" description:"用户序号" json:"uid"`
	Username    string     `orm:"unique" description:"用户名" json:"username"`
	Password    string     `description:"用户密码" json:"password"`
	Role        *Role      `orm:"rel(fk);null" description:"角色" json:"Role"`
}
// 各种ORM查询方法请自行实现,这里不强调

7.定义用户控制器Controller

CasbinRestful ApiCasbin管理员用户匿名

controllers/User.go

type UserController struct {
	beego.Controller
}
// 只有管理员才能注册
// @router  /register [post]
func (c *UserController) Register(){}

// 只有用户、管理员才能看到别人或者自己的个人资料
// 因为管理员继承用户,所以用户能做到的,管理员也可
// @router  /profile [get]
func (c *UserController) Profile(){}

// 匿名也能登陆
// @router /login [post]
func (c *UserController) Login(){}
Policy

controllers/User.go

func registerUserPolicy() {
    // Path前缀,这个根据具体项目自行调整
	api := "/v1/user"
	// 路由的Policy
	adminPolicy := map[string][]string{
	    "/register": {"post"},
	}
	userPolicy := map[string][]string{
	    // 注意 - casbin.conf中使用 keyMatch2 对 obj 进行
	    // 验证,这里要使用 :id 来对参数进行标识
	    "/:id": {"get", "put", "delete"},
	}
	anonymousPolicy := map[string][]string{
		"/login":  {"post"},
	}
	// models.RoleAdmin      = "admin"
	// models.RoleUser       = "user"
	// models.RoleAnonymous  = "anonymous"
	AddPolicyFromController(models.RoleAdmin, adminPolicy, api)
	AddPolicyFromController(models.RoleUser, userPolicy, api)
	AddPolicyFromController(models.RoleAnonymous, anonymousPolicy, api)
}
func AddPolicyFromController(role string, policy map[string][]string, api string) {
	for path := range policy {
		for _, method := range policy[path] {
		    // models.Enforcer在models/Casbin.go中定义并初始化
			_ = models.Enforcer.AddPolicy(models.GetRoleString(role), fmt.Sprintf("%s%s", api, path), method)
		}
	}
}

最后别忘了初始化调用方法:

controllers/User.go

func init(){
    registerUserPolicy()
}

Casbin
BeegoauthzCasbinauthzBasicAuthorizerEnforcer

filters/User.go(自己新建)

type BasicAuthorizer struct {
	enforcer *casbin.Enforcer
}
SessionSession

filters/User.go

func (a *BasicAuthorizer) GetUserRole(input *context.BeegoInput) string {
	user, ok := input.Session("user").(*models.User)
	// 判断是否成功通过Session获取用户信息
	if !ok || user.Role.Name == "" {
	    // 不成功的话直接返回匿名
		return models.GetRoleString(models.RoleAnonymous)
	}
	return user.Role.Name
}
beego.FilterFuncCasbinEnforce布尔值

filters/User.go

func NewAuthorizer(e *casbin.Enforcer) beego.FilterFunc {
	return func(ctx *context.Context) {
	    // 通过创建结构体,存放Enforcer
		a := &BasicAuthorizer{enforcer: e}
		// 获取用户角色
		userRole := a.GetUserRole(ctx.Input)
		// 获取访问路径
		method := strings.ToLower(ctx.Request.Method)
		// 获取访问方式
		path := strings.ToLower(ctx.Request.URL.Path)
		// 进行验证 - 失败则返回401
		if status := a.enforcer.Enforce(userRole, path, method); !status {
			ctx.Output.Status = 401
			_ = ctx.Output.JSON(map[string]string{"msg": "用户权限不足"}, beego.BConfig.RunMode != "prod", false)
		}
	}
}
Casbin

最后我们还得把这个过滤器附加上:

main.go

func main() {
	......
	beego.InsertFilter("/v1/user/*", beego.BeforeRouter, filters.NewAuthorizer(models.Enforcer))
	......
	beego.Run()
}

9.总结

CasbinCasbin