侧边栏壁纸
博主头像
SeaDream乄造梦

Dream,Don't stop a day of hard and don't give up a little hope。 ——不停止一日努力&&不放弃一点希望。

  • 累计撰写 101 篇文章
  • 累计创建 21 个标签
  • 累计收到 15 条评论

目 录CONTENT

文章目录

gin-vue-admin改多租户模式(casbin--RBAC)

SeaDream乄造梦
2025-09-30 / 0 评论 / 0 点赞 / 4 阅读 / 3,732 字
温馨提示:
亲爱的,如果觉得博主很有趣就留下你的足迹,并收藏下链接在走叭

流程

  1. 数据库层面
  2. 后端casbin层面
  3. 前端设置角色层面
  4. 后端设置角色接口层面

数据库层面

3 列数据存放情况是:

  • v0 = sub (角色)
  • v1 = obj (接口地址)
  • v2 = act (方法)

要改成 4 列(sub,dom,obj,act),需要:

  • v0 = sub (角色,比如 1)
  • v1 = dom (域,补 *)
  • v2 = obj (接口地址)
  • v3 = act (方法)
-- 先备份一份,避免数据丢失
CREATE TABLE casbin_rule_backup AS SELECT * FROM casbin_rule;

-- 把角色=1 的数据更新成 4 列形式
UPDATE casbin_rule
SET v3 = v2,   -- 方法挪到 v3
    v2 = v1,   -- obj 挪到 v2
    v1 = '*'   -- 补上 dom="*"

代码层面

image.png

casbin_rbac.go


// CasbinHandler 拦截器
func CasbinHandler() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 从上下文获取租户 ID(假设整形)
		/*tidI, exists := c.Get("tenant_id")
		if !exists {
			// 如果没有 tenant,按策略拒绝或给默认租户
			response.FailWithDetailed(gin.H{}, "租户标识缺失", c)
			c.Abort()
			return
		}
		// 把 domain 转为字符串形式(Casbin 的 domain 用字符串)
		var domain string
		switch v := tidI.(type) {
		case int:
			domain = strconv.Itoa(v)
		case int64:
			domain = strconv.FormatInt(v, 10)
		case string:
			domain = v
		default:
			domain = fmt.Sprintf("%v", v)
		}*/

		// 获取 user / sub(这里用你的 AuthorityId,或可改为用户名/ID string)
		waitUse, _ := utils.GetClaims(c)
		sub := strconv.Itoa(int(waitUse.AuthorityId))

		domain := strconv.Itoa(int(waitUse.TenantId))
		// 打印日志  casbin sub domain obj act
		// 获取请求路径、方法
		path := c.Request.URL.Path
		// obj 要 “去前缀” 的处理(同你原来那样)
		obj := strings.TrimPrefix(path, global.GVA_CONFIG.System.RouterPrefix)
		act := c.Request.Method
		zap.L().Info("casbin sub domain obj act", zap.String("sub", sub), zap.String("domain", domain), zap.String("obj", obj), zap.String("act", act))

		// 调用 Casbin:改为带 domain 的 enforce
		e := utils.GetCasbin()
		ok, err := e.Enforce(sub, domain, obj, act)
		if err != nil {
			// 如果 enforce 出错,记录日志、返回错误
			zap.L().Error("casbin enforce error", zap.Error(err))
			response.FailWithDetailed(gin.H{}, "权限校验异常", c)
			c.Abort()
			return
		}
		if !ok {
			response.FailWithDetailed(gin.H{}, "权限不足", c)
			c.Abort()
			return
		}

		c.Next()
	}
}


casbin_utils.go

package utils

import (
	"sync"

	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
	gormadapter "github.com/casbin/gorm-adapter/v3"
	"github.com/flipped-aurora/gin-vue-admin/server/global"
	"go.uber.org/zap"
)

var (
	syncedCachedEnforcer *casbin.SyncedCachedEnforcer
	once                 sync.Once
)

// GetCasbin 获取casbin实例
func GetCasbin() *casbin.SyncedCachedEnforcer {
	once.Do(func() {
		a, err := gormadapter.NewAdapterByDB(global.GVA_DB)
		if err != nil {
			zap.L().Error("适配数据库失败请检查casbin表是否为InnoDB引擎!", zap.Error(err))
			return
		}
		text := `
          [request_definition]
          r = sub, dom, obj, act
          
          [policy_definition]
          p = sub, dom, obj, act
          
          [role_definition]
          g = _, _, _
          
          [policy_effect]
          e = some(where (p.eft == allow))
          
          [matchers]
          # Allow if:
          #  - policy domain is "*" (global) OR equals request domain
          #  - AND subject has role in the domain (or in "*" global domain)
          #  - AND object match & action match
          m = (p.dom == "*" || p.dom == r.dom) && (g(r.sub, p.sub, r.dom) || g(r.sub, p.sub, "*")) && keyMatch2(r.obj, p.obj) && r.act == p.act
          `

		m, err := model.NewModelFromString(text)
		if err != nil {
			zap.L().Error("Casbin 模型加载失败!", zap.Error(err))
			return
		}

		syncedCachedEnforcer, err = casbin.NewSyncedCachedEnforcer(m, a)
		if err != nil {
			zap.L().Error("Casbin Enforcer 初始化失败!", zap.Error(err))
			return
		}

		syncedCachedEnforcer.SetExpireTime(60 * 60)
		if err := syncedCachedEnforcer.LoadPolicy(); err != nil {
			zap.L().Error("加载策略失败!", zap.Error(err))
		}
	})
	return syncedCachedEnforcer
}


jwt.go

package request

import (
	jwt "github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
)

// CustomClaims structure
type CustomClaims struct {
	BaseClaims
	BufferTime int64
	jwt.RegisteredClaims
}

type BaseClaims struct {
	UUID        uuid.UUID
	ID          uint
	Username    string
	NickName    string
	AuthorityId uint
	TenantId    int64
}


clams.go

func LoginToken(user system.Login) (token string, claims systemReq.CustomClaims, err error) {
	j := NewJWT()

	claims = j.CreateClaims(systemReq.BaseClaims{
		UUID:        user.GetUUID(),
		ID:          user.GetUserId(),
		NickName:    user.GetNickname(),
		Username:    user.GetUsername(),
		AuthorityId: user.GetAuthorityId(),
		TenantId:    user.GetTenantId(),
	})
	token, err = j.CreateToken(claims)
	return
}

0

评论区