流程
- 数据库层面
- 后端casbin层面
- 前端设置角色层面
- 后端设置角色接口层面
数据库层面
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="*"
代码层面
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
}
评论区