gin casbin xorm vue-admin权限认证。

Posted bfyang5130

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了gin casbin xorm vue-admin权限认证。相关的知识,希望对你有一定的参考价值。

1、因为用到是gin所以直接指定rbac吧。

一般都是使用:角色,操作路径,操作类型来做权限。如果还要弄更多,这里就不涉及更多了

首先配置文件

[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) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")

2、从数据库加载角色配置

package main

import (
	"github.com/casbin/casbin/v2"
	_ "github.com/lib/pq"

	"github.com/casbin/xorm-adapter"
)

func main() {
	// Initialize a Xorm adapter and use it in a Casbin enforcer:
	// The adapter will use the Postgres database named "casbin".
	// If it doesn‘t exist, the adapter will create it automatically.
	a, _ := xormadapter.NewAdapter("postgres", "user=postgres_username password=postgres_password host=127.0.0.1 port=5432 sslmode=disable") // Your driver and data source.

	// Or you can use an existing DB "abc" like this:
	// The adapter will use the table named "casbin_rule".
	// If it doesn‘t exist, the adapter will create it automatically.
	// a := xormadapter.NewAdapter("postgres", "dbname=abc user=postgres_username password=postgres_password host=127.0.0.1 port=5432 sslmode=disable", true)

	e, _ := casbin.NewEnforcer("../examples/rbac_model.conf", a)

	// Load the policy from DB.
	e.LoadPolicy()
}

 上面这个是官方提供的从数据加载权限配置的案例。里面会给自动生成数据库,自动生成表。

    那么作为一个要集成到自己项目里面的功能,有一个表跟自己的表。。。nb_开头太格格不入了。所以可以拿它出来进行改写。

// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils

import (
	"errors"
	model2 "notebooks/model"
	"runtime"
	"strings"

	"github.com/casbin/casbin/v2/model"
	"github.com/casbin/casbin/v2/persist"
	"github.com/lib/pq"
	"github.com/xormplus/xorm"
)

// Adapter represents the Xorm adapter for policy storage.
type Adapter struct {
	driverName     string
	dataSourceName string
	dbSpecified    bool
	engine         *xorm.Engine
}

// finalizer is the destructor for Adapter.
func finalizer(a *Adapter) {
	err := a.engine.Close()
	if err != nil {
		panic(err)
	}
}

// NewAdapter is the constructor for Adapter.
// dbSpecified is an optional bool parameter. The default value is false.
// It‘s up to whether you have specified an existing DB in dataSourceName.
// If dbSpecified == true, you need to make sure the DB in dataSourceName exists.
// If dbSpecified == false, the adapter will automatically create a DB named "casbin".
func NewAdapter() (*Adapter, error) {
	a := &Adapter{}
	// Open the DB, create it if not existed.
	err := a.open()
	if err != nil {
		return nil, err
	}

	// Call the destructor when the object is released.
	runtime.SetFinalizer(a, finalizer)

	return a, nil
}
//通过已有的engine生成
func NewAdapterByEngine(engine *xorm.Engine) (*Adapter, error) {
	a := &Adapter{
		engine: engine,
	}

	err := a.createTable()
	if err != nil {
		return nil, err
	}

	return a, nil
}
//生成数据库
func (a *Adapter) createDatabase() (err error) {

	if a.driverName == "postgres" {
		if _, err = AppEngine.Exec("CREATE DATABASE casbin"); err != nil {
			// 42P04 is	duplicate_database
			if pqerr, ok := err.(*pq.Error); ok && pqerr.Code == "42P04" {
				AppEngine.Close()
				return nil
			}
		}
	} else if a.driverName != "sqlite3" {
		_, err = AppEngine.Exec("CREATE DATABASE IF NOT EXISTS casbin")
	}
	if err != nil {
		AppEngine.Close()
		return err
	}

	return AppEngine.Close()
}

func (a *Adapter) open() error {
	a.engine = AppEngine

	return a.createTable()
}

func (a *Adapter) close() error {
	err := a.engine.Close()
	if err != nil {
		return err
	}

	a.engine = nil
	return nil
}

func (a *Adapter) createTable() error {
	return a.engine.Sync2(new(model2.NbCasbinRule))
}

func (a *Adapter) dropTable() error {
	return a.engine.DropTables(new(model2.NbCasbinRule))
}

//原生加载一条权限
func loadPolicyLine(line *model2.NbCasbinRule, model model.Model) {
	const prefixLine = ", "
	var sb strings.Builder

	sb.WriteString(line.PType)
	if len(line.V0) > 0 {
		sb.WriteString(prefixLine)
		sb.WriteString(line.V0)
	}
	if len(line.V1) > 0 {
		sb.WriteString(prefixLine)
		sb.WriteString(line.V1)
	}
	if len(line.V2) > 0 {
		sb.WriteString(prefixLine)
		sb.WriteString(line.V2)
	}
	if len(line.V3) > 0 {
		sb.WriteString(prefixLine)
		sb.WriteString(line.V3)
	}
	if len(line.V4) > 0 {
		sb.WriteString(prefixLine)
		sb.WriteString(line.V4)
	}
	if len(line.V5) > 0 {
		sb.WriteString(prefixLine)
		sb.WriteString(line.V5)
	}

	persist.LoadPolicyLine(sb.String(), model)
}

// 从数据库查询权限
func (a *Adapter) LoadPolicy(model model.Model) error {
	var lines []*model2.NbCasbinRule
	if err := a.engine.Find(&lines); err != nil {
		return err
	}
   //一条一条地加载
	for _, line := range lines {
		loadPolicyLine(line, model)
	}

	return nil
}

//这个是配置一条权限,返回一条数据
func savePolicyLine(ptype string, rule []string, colId int) *model2.NbCasbinRule {
	line := &model2.NbCasbinRule{PType: ptype}

	l := len(rule)
	if l > 0 {
		line.V0 = rule[0]
	}
	if l > 1 {
		line.V1 = rule[1]
	}
	if l > 2 {
		line.V2 = rule[2]
	}
	if l > 3 {
		line.V3 = rule[3]
	}
	if l > 4 {
		line.V4 = rule[4]
	}
	if l > 5 {
		line.V5 = rule[5]
	}
    line.ColId=colId
	return line
}

// 这个是将文件里写好的配置文件,写入数据库不能使用
func (a *Adapter) SavePolicy(model model.Model) error {
	if 1==1{
		return errors.New("不能使用本方法")
	}
	return nil
	/*
	err := a.dropTable()
	if err != nil {
		return err
	}
	err = a.createTable()
	if err != nil {
		return err
	}

	var lines []*model2.NbCasbinRule

	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.engine.Insert(&lines)
	return err
	*/
}

// 添加一条权限,但是只是添加,没有入配置的,要重新使用loadPolicy(看情况处理,后期可能要做一下性能处理,再加载)
func (a *Adapter) AddPolicyNew(sec string, ptype string, rule []string,colId int) error {
	line := savePolicyLine(ptype, rule,colId)
	_, err := a.engine.Insert(line)
	return err
}
// 添加一条权限,但是只是添加,没有入配置的,要重新使用loadPolicy(看情况处理,后期可能要做一下性能处理,再加载)
func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
	line := savePolicyLine(ptype, rule,0)
	_, err := a.engine.Insert(line)
	return err
}
// 删除一条权限,同样没有入配置(看情况处理,后期可能要做一下性能处理,再加载)
func (a *Adapter) RemovePolicyNew(sec string, ptype string, rule []string,colId int) error {
	line := savePolicyLine(ptype, rule,colId)
	_, err := a.engine.Delete(line)
	return err
}
// 删除一条权限,同样没有入配置(看情况处理,后期可能要做一下性能处理,再加载)
func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
	line := savePolicyLine(ptype, rule,0)
	_, err := a.engine.Delete(line)
	return err
}

// 根据配置删除相应的一条权限(少用吧),同样要重新加载
func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
	if 1==1{
		return errors.New("不能使用本方法")
	}
	line := &model2.NbCasbinRule{PType: ptype}

	idx := fieldIndex + len(fieldValues)
	if fieldIndex <= 0 && idx > 0 {
		line.V0 = fieldValues[0-fieldIndex]
	}
	if fieldIndex <= 1 && idx > 1 {
		line.V1 = fieldValues[1-fieldIndex]
	}
	if fieldIndex <= 2 && idx > 2 {
		line.V2 = fieldValues[2-fieldIndex]
	}
	if fieldIndex <= 3 && idx > 3 {
		line.V3 = fieldValues[3-fieldIndex]
	}
	if fieldIndex <= 4 && idx > 4 {
		line.V4 = fieldValues[4-fieldIndex]
	}
	if fieldIndex <= 5 && idx > 5 {
		line.V5 = fieldValues[5-fieldIndex]
	}

	_, err := a.engine.Delete(line)
	return err
}

 

在这里,直接把里面的生成结构写成与自己风格一致的struct,并放在model里,这样,它就不会生成原版的表,而是生成model里的表了,在里面使用到的engine可以使用自己全局的定义的db engine。保证配置数据库连接只有一个地方。

里面的方法基本上根据自己的业务写在自己的service里,并不一定要用它原来的写法。

3、到这里,已经可以从数据库里加载自己想要的权限了。那么,要怎么放入权限呢

casbinrule原表有,ptype,v0,v1,v2,v3,v4,v5.目前只使用到ptype,v0,v1,v2

ptype写死为p,v0为角色,v1为操作路径v2为方式,如:ptype:p,v0:supperAdmin,v1:/money/all_in_my_card,v2:POST

说明supperAdmin有路径/money/all_in_my_card操作post的权限

根据自己的业务要求,把对应的数据生成放到表后。下面使用中间件来进行验证

func CheckPermission() gin.HandlerFunc {
	return func(c *gin.Context) {
		//根据上下文获取载荷claims 从claims获得role
		strClaims, has := c.Get("claims")
		if has {
			claims := strClaims.(*CustomClaims)
			role := claims.Role
			if len(role) < 3 {
				c.JSON(http.StatusOK, gin.H{
					"errno":  403,
					"errmsg": "验证权限出错",
				})
				c.Abort()
				return
			}
			rolsString := role[1 : len(role)-1]
			roleArr := strings.Split(rolsString, ",")
			var fitErr error
			var has bool
			var err error
			for _, oneRole := range roleArr {
				has, err = utils.CasbinEn.Enforce( oneRole, c.Request.URL.Path, c.Request.Method)
				if err != nil {
					fitErr = err
					break
				}
				if has {
					break
				}
			}
			if fitErr != nil {
				c.JSON(http.StatusOK, gin.H{
					"errno":  403,
					"errmsg": "验证权限出错",
				})
				c.Abort()
				return
			}
			if has {
				c.Next()
			} else {
				c.JSON(http.StatusOK, gin.H{
					"errno": 403,
					"msg":   "很抱歉您没有此权限",
				})
				c.Abort()
				return
			}
		} else {
			c.Next()
		}
	}
}

  

4、上面是处理好的后台权限的控制。(需搭配jwt验证一起使用)。那么vue-admin里怎么控制呢.

vue-admin有一个perms的权限处理。在后台生成"perms":["GET /sys/admin","GET /sys/log","GET /sys/os","GET /sys/role"]给到前台就行。

其实就是根据用户的角色,查询casbinrule里的v1,v2,组成json list返回就可以。

 

上面是这段时间改写casbin权限用到的一点看法,以及实际的步骤。  

以上是关于gin casbin xorm vue-admin权限认证。的主要内容,如果未能解决你的问题,请参考以下文章

go gin+casbin RBAC 简单例子

Casbin + Gin + Gorm 学习探索

基于框架gin+xorm搭建的MVC项目

golang的xorm如何将[]map[string][]byte 格式的数据序列化成json输出

从别人的代码中学习golang系列--03

GoCN每日新闻(2019-10-02)