Go Gin Gorm Casbin权限管理实现 - 2. 使用Gorm存储Casbin权限配置以及`增删改查`

news2024/11/21 2:29:23

文章目录

  • 0. 背景
  • 1. 准备工作
  • 2. 权限配置以及`增删改查`
    • 2.1 策略和组使用规范
    • 2.2 用户以及组关系的增删改查
      • 2.2.1 获取所有用户以及关联的角色
      • 2.2.2 角色组中添加用户
      • 2.2.3 角色组中删除用户
    • 2.3 角色组权限的`增删改查`
      • 2.3.1 获取所有角色组权限
      • 2.3.2 创建角色组权限
      • 2.3.3 修改角色组权限
      • 2.3.4 删除角色组权限
  • 3. 测试以及完整代码
    • 3.1 casbin_service.go
    • 3.2 casbin_service_test.go
    • 3.3测试结果
  • 4. 结语

0. 背景

Casbin是用于Golang项目的功能强大且高效的开源访问控制库。
强大通用也意味着概念和配置较多,具体到实际应用(以Gin Web框架开发)需要解决以下问题:

  • 权限配置的存储,以及增删改查
  • Gin框架的中间件如何实现

经过一番摸索实践出经验,计划分为三个章节,循序渐进的介绍使用方法

  1. Casbin概念介绍以及库使用
  2. 使用Gorm存储Casbin权限配置以及增删改查
  3. 实现Gin鉴权中间件

1. 准备工作

接上一章,略改进一下,将model.conf文件内容存储到go字符串中,最终代码如下:

package main

import (
	"log"

	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
	gormadapter "github.com/casbin/gorm-adapter/v3"
	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
)

func main() {
	db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}

	a, err := gormadapter.NewAdapterByDB(db)
	if err != nil {
		panic("new gorm adapter error: " + err.Error())
	}

	m, err := model.NewModelFromString(`[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 = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act`)
	if err != nil {
		panic("new model error: " + err.Error())
	}

	e, err := casbin.NewEnforcer(m, a)
	if err != nil {
		panic("new casbin enforcer error: " + err.Error())
	}

	e.LoadPolicy()
	// 添加策略
	ok, err := e.AddPolicy("admin", "/api/user", "GET")
	log.Println("add admin /api/user GET: ", ok, err)
	ok, err = e.AddGroupingPolicy("leo", "admin")
	log.Println("add leo to admin group: ", ok, err)
	e.SavePolicy()

	ok, err = e.Enforce("leo", "/api/user", "GET")
	log.Println("leo GET /api/user :", ok, err)
	ok, err = e.Enforce("leo", "/api/user", "DELETE")
	log.Println("leo DELETE /api/user :", ok, err)
}

上述代码默认使用的gormadapter.CasbinRule对应的Go结构和数据库表如下

type CasbinRule struct {
	ID    uint   `gorm:"primaryKey;autoIncrement"`
	Ptype string `gorm:"size:100"`
	V0    string `gorm:"size:100"`
	V1    string `gorm:"size:100"`
	V2    string `gorm:"size:100"`
	V3    string `gorm:"size:100"`
	V4    string `gorm:"size:100"`
	V5    string `gorm:"size:100"`
}

2. 权限配置以及增删改查

以上准备知识,仅了解和casbin基本操作以及如何配合gorm存储到DB中,还需要完善权限,以及提供增删改查操作

2.1 策略和组使用规范

casbin的policy十分灵活,具体到自己业务使用中,我这里按以下两条规则使用,基本能满足业务需求

  1. 所有策略只针对角色组设置
  2. 用户关联到组(一个用户可以有多个组)

如下,两个角色组adminuser组,admin组查询删除用户,user组只能查询用户

ptypev0v1v2v3v4v5
padmin/api/userGET
padmin/api/userDELETE
puser/api/userGET
gleoadmin
gleo2user

基础代码如下,下一节给CasbinService添加增删改查功能

package main

import (
	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
	gormadapter "github.com/casbin/gorm-adapter/v3"
	"gorm.io/gorm"
)

type CasbinService struct {
	enforcer *casbin.Enforcer
	adapter  *gormadapter.Adapter
}

func NewCasbinService(db *gorm.DB) (*CasbinService, error) {
	a, err := gormadapter.NewAdapterByDB(db)
	if err != nil {
		return nil, err
	}
	m, err := model.NewModelFromString(`[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 = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act`)
	if err != nil {
		return nil, err
	}
	e, err := casbin.NewEnforcer(m, a)
	if err != nil {
		return nil, err
	}
	return &CasbinService{adapter: a, enforcer: e}, nil
}

2.2 用户以及组关系的增删改查

2.2.1 获取所有用户以及关联的角色

根据2.1中示范数据,预期输出

[{
	"username": "leo",
	"roleNames": ["admin"]
}, {
	"username": "leo2",
	"roleNames": ["user"]
}]

获取所有用户以及关联的角色代码片段

type User struct {
	UserName  string
	RoleNames []string
}

// 获取所有用户以及关联的角色
func (c *CasbinService) GetUsers() (users []User) {
	p := c.enforcer.GetGroupingPolicy()
	usernameUser := make(map[string]*User, 0)
	for _, _p := range p {
		username, usergroup := _p[0], _p[1]
		if v, ok := usernameUser[username]; ok {
			usernameUser[username].RoleNames = append(v.RoleNames, usergroup)
		} else {
			usernameUser[username] = &User{UserName: username, RoleNames: []string{usergroup}}
		}
	}
	for _, v := range usernameUser {
		users = append(users, *v)
	}
	return
}

2.2.2 角色组中添加用户

// 角色组中添加用户, 没有组默认创建
func (c *CasbinService) UpdateUserRole(username, rolename string) error {
	_, err := c.enforcer.AddGroupingPolicy(username, rolename)
	return err
}

2.2.3 角色组中删除用户

// 角色组中删除用户
func (c *CasbinService) DeleteUserRole(username, rolename string) error {
	_, err := c.enforcer.RemoveGroupingPolicy(username, rolename)
	return err
}

2.3 角色组权限的增删改查

2.3.1 获取所有角色组权限

根据2.1中示范数据,预期输出形式

[{
	"roleName": "admin",
	"url": "/api/user",
	"method": "DELETE"
}, {
	"roleName": "admin",
	"url": "/api/user",
	"method": "GET"
}, {
	"roleName": "user",
	"url": "/api/user",
	"method": "GET"
}]

获取所有角色组权限

```go
// (RoleName, Url, Method) 对应于 `CasbinRule` 表中的 (v0, v1, v2)
type RolePolicy struct {
	RoleName string `gorm:"column:v0"`
	Url      string `gorm:"column:v1"`
	Method   string `gorm:"column:v2"`
}

// 获取所有角色组权限
func (c *CasbinService) GetRolePolicy() (roles []RolePolicy, err error) {
	err = c.adapter.GetDb().Model(&gormadapter.CasbinRule{}).Where("ptype = 'p'").Find(&roles).Error
	if err != nil {
		return nil, err
	}
	return
}

2.3.2 创建角色组权限

// 创建角色组权限, 已有的会忽略
func (c *CasbinService) CreateRolePolicy(r RolePolicy) error {
	// 不直接操作数据库,利用enforcer简化操作
	err := c.enforcer.LoadPolicy()
	if err != nil {
		return err
	}
	_, err = c.enforcer.AddPolicy(r.RoleName, r.Url, r.Method)
	if err != nil {
		return err
	}
	return c.enforcer.SavePolicy()
}

2.3.3 修改角色组权限

// 修改角色组权限
func (c *CasbinService) UpdateRolePolicy(old, new RolePolicy) error {
	_, err := c.enforcer.UpdatePolicy([]string{old.RoleName, old.Url, old.Method},
		[]string{new.RoleName, new.Url, new.Method})
	if err != nil {
		return err
	}
	return c.enforcer.SavePolicy()
}

2.3.4 删除角色组权限

// 删除角色组权限
func (c *CasbinService) DeleteRolePolicy(r RolePolicy) error {
	_, err := c.enforcer.RemovePolicy(r.RoleName, r.Url, r.Method)
	if err != nil {
		return err
	}
	return c.enforcer.SavePolicy()
}

3. 测试以及完整代码

3.1 casbin_service.go

package main

import (
	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
	gormadapter "github.com/casbin/gorm-adapter/v3"
	"gorm.io/gorm"
)

/*
按如下约定:
  1. 所有策略只针对角色组设置
  2. 用户关联到组(一个用户可以有多个组)
+-------+-------+-----------+--------+----+----+----+
| ptype | v0    | v1        | v2     | v3 | v4 | v5 |
+-------+-------+-----------+--------+----+----+----+
| p     | admin | /api/user | GET    |    |    |    |
+-------+-------+-----------+--------+----+----+----+
| p     | admin | /api/user | DELETE |    |    |    |
+-------+-------+-----------+--------+----+----+----+
| p     | user  | /api/user | GET    |    |    |    |
+-------+-------+-----------+--------+----+----+----+
| ...   | ...   | ...       |        |    |    |    |
+-------+-------+-----------+--------+----+----+----+
| g     | leo   | admin     |        |    |    |    |
+-------+-------+-----------+--------+----+----+----+
| g     | leo2  | admin     |        |    |    |    |
+-------+-------+-----------+--------+----+----+----+
*/
type CasbinService struct {
	enforcer *casbin.Enforcer
	adapter  *gormadapter.Adapter
}

func NewCasbinService(db *gorm.DB) (*CasbinService, error) {
	a, err := gormadapter.NewAdapterByDB(db)
	if err != nil {
		return nil, err
	}
	m, err := model.NewModelFromString(`[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 = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act`)
	if err != nil {
		return nil, err
	}
	e, err := casbin.NewEnforcer(m, a)
	if err != nil {
		return nil, err
	}
	return &CasbinService{adapter: a, enforcer: e}, nil
}

// (RoleName, Url, Method) 对应于 `CasbinRule` 表中的 (v0, v1, v2)
type RolePolicy struct {
	RoleName string `gorm:"column:v0"`
	Url      string `gorm:"column:v1"`
	Method   string `gorm:"column:v2"`
}

// 获取所有角色组权限
func (c *CasbinService) GetRolePolicy() (roles []RolePolicy, err error) {
	err = c.adapter.GetDb().Model(&gormadapter.CasbinRule{}).Where("ptype = 'p'").Find(&roles).Error
	if err != nil {
		return nil, err
	}
	return
}

// 创建角色组权限, 已有的会忽略
func (c *CasbinService) CreateRolePolicy(r RolePolicy) error {
	// 不直接操作数据库,利用enforcer简化操作
	err := c.enforcer.LoadPolicy()
	if err != nil {
		return err
	}
	_, err = c.enforcer.AddPolicy(r.RoleName, r.Url, r.Method)
	if err != nil {
		return err
	}
	return c.enforcer.SavePolicy()
}

// 修改角色组权限
func (c *CasbinService) UpdateRolePolicy(old, new RolePolicy) error {
	_, err := c.enforcer.UpdatePolicy([]string{old.RoleName, old.Url, old.Method},
		[]string{new.RoleName, new.Url, new.Method})
	if err != nil {
		return err
	}
	return c.enforcer.SavePolicy()
}

// 删除角色组权限
func (c *CasbinService) DeleteRolePolicy(r RolePolicy) error {
	_, err := c.enforcer.RemovePolicy(r.RoleName, r.Url, r.Method)
	if err != nil {
		return err
	}
	return c.enforcer.SavePolicy()
}

type User struct {
	UserName  string
	RoleNames []string
}

// 获取所有用户以及关联的角色
func (c *CasbinService) GetUsers() (users []User) {
	p := c.enforcer.GetGroupingPolicy()
	usernameUser := make(map[string]*User, 0)
	for _, _p := range p {
		username, usergroup := _p[0], _p[1]
		if v, ok := usernameUser[username]; ok {
			usernameUser[username].RoleNames = append(v.RoleNames, usergroup)
		} else {
			usernameUser[username] = &User{UserName: username, RoleNames: []string{usergroup}}
		}
	}
	for _, v := range usernameUser {
		users = append(users, *v)
	}
	return
}

// 角色组中添加用户, 没有组默认创建
func (c *CasbinService) UpdateUserRole(username, rolename string) error {
	_, err := c.enforcer.AddGroupingPolicy(username, rolename)
	return err
}

// 角色组中删除用户
func (c *CasbinService) DeleteUserRole(username, rolename string) error {
	_, err := c.enforcer.RemoveGroupingPolicy(username, rolename)
	return err
}

3.2 casbin_service_test.go

package main

import (
	"testing"

	"github.com/glebarez/sqlite"
	"gorm.io/gorm"
)

func TestCasbinService(t *testing.T) {
	db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}
	s, err := NewCasbinService(db)
	if err != nil {
		t.Fatalf("new service error: %v", err)
	}

	// 创建个用户,分别关联到`admin`和`user`组, `leo2`既是`admin`组又是`user`组
	t.Logf("create user: leo with group: admin result: %v", s.UpdateUserRole("leo", "admin"))
	t.Logf("create user: leo with group: admin result: %v", s.UpdateUserRole("leo2", "admin"))
	t.Logf("create user: leo with group: admin result: %v", s.UpdateUserRole("leo2", "user"))
	t.Log()
	t.Logf("users is: %v\n", s.GetUsers())

	// 针对`admin`和`user`组创建三条策略
	t.Log("create admin /api/user GET: ", s.CreateRolePolicy(RolePolicy{RoleName: "admin", Url: "/api/user", Method: "GET"}))
	t.Log("create admin /api/user DELETE: ", s.CreateRolePolicy(RolePolicy{RoleName: "admin", Url: "/api/user", Method: "DELETE"}))
	t.Log("create user /api/user GET: ", s.CreateRolePolicy(RolePolicy{RoleName: "user", Url: "/api/user", Method: "GET"}))
	t.Log("all policy is: ")
	t.Log(s.GetRolePolicy())

	t.Log("delete admin /api/user GET", s.DeleteRolePolicy(RolePolicy{RoleName: "admin", Url: "/api/user", Method: "GET"}))
}


3.3测试结果

在这里插入图片描述
在这里插入图片描述

4. 结语

基本的权限模型设计以及操作函数均已设计正常, 下一章开始结合gin框架设计一个中间件, 实现casbin权限验证

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1064218.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app 经验分享,从入门到离职(实战篇)——模拟从后台获取图片路径数据后授权相册以及保存图片

文章目录 📋前言⏬关于专栏 🎯需求描述🎯前置知识点🧩uni.showLoading()🧩uni.authorize()🧩uni.downloadFile()🧩uni.saveImageToPhotosAlbum() 🎯演示代码🧩关于图片接…

不能一棍子敲死刚诞生不久的USB-C,虽然它有时确实很惹人厌

今年iPhone机型最大的预期之一是从苹果专有的Lightning端口过渡到USB-C标准。一些人担心过渡需要他们更换所有配件,而另一些人(包括你)则期待着未来能够为iPad、MacBook和iPhone使用一根电缆。 然而,现在新机型已经问世&#xff…

微信小程序使用路由传参和传对象的方法

近期在做微信小程序开发,在页面跳转时,需要携带参数到下一个页面,尤其是将对象传入页面。为了方便重温,特此记录。 路由传字符串参数 原始页面 传递字符串参数比较简单。路由跳转有两种方式,一种是通过navigator组件…

【AI视野·今日CV 计算机视觉论文速览 第262期】Fri, 6 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 6 Oct 2023 Totally 73 papers 👉上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Improved Baselines with Visual Instruction Tuning Authors Haotian Liu, Chunyuan Li, Yuheng Li, Yong Jae Lee大型多模…

java Spring Boot在配置文件中关闭热部署

之前更大家一起搭建了一个热部署的开发环境 但是 大家要清楚一个情况 我们线上程序运行突然内部发生变化这是不可能的。 所以 他就只会对我们开发环境有效 是否开启 我们可以通过 application配置文件来完成 我这里是yml格式的 参考代码如下 spring:devtools:restart:enabled…

雷达天线、信号链路、大气衰减基本概念

一、 有效各向同性辐射功率 有效各向同性辐射功率是各向同性天线必须辐射的假设功率,以在天线最强波束的方向上提供与实际源天线相同(等效)的信号强度。 二、电磁波传播 图1 从发射机到接收机的链路路径 信号与环境传播模型远离地面的高频和/或链路路径和/或窄天线Line of …

基础算法之——【动态规划之路径问题】1

今天更新动态规划路径问题1,后续会继续更新其他有关动态规划的问题!动态规划的路径问题,顾名思义,就是和路径相关的问题。当然,我们是从最简单的找路径开始! 动态规划的使用方法: 1.确定状态并…

《视觉 SLAM 十四讲》V2 第 6 讲 非线性优化 【高斯牛顿法、列文伯格-马夸尔特方法 、Ceres 库 和 g2o库 】

文章目录 6.1.2 最小二乘 寻找下降增量 Δ x k \Delta\bm{x}_k Δxk​的 4 种方法6.2.1 一阶和二阶梯度法(最速下降法、牛顿法)6.2.2 高斯牛顿法6.2.3 列文伯格-马夸尔特方法 【阻尼牛顿法】【信赖区域法】 6.3 实践6.3.1 手写高斯牛顿法 【Code】6.3.2 谷歌的优化库 Ceres 【最…

自动驾驶学习笔记(一)——Apollo平台

#Apollo开发者社区# 学习课程的传送门如下,当您也准备学习自动驾驶时,可以和我一同前往: 《自动驾驶新人之旅》免费课程—> 传送门 《2023星火培训【感知专项营】》免费课程—>传送门 文章目录 前言 Apollo框架 开发平台 总结 前…

【TMS320F28374之PWM】

简介 增强型脉宽调制器(ePWM)外设是控制商业和工业设备中许多电力电子系统的关键元件。这些系统包括数字电机控制、开关模式电源控制、不间断电源(UPS)和其他形式的电源转换。ePWM外设还可以执行数模转换(DAC)功能,其中占空比相当于DAC模拟值;它有时被称为电源DAC。…

[机缘参悟-109] :接纳生活中的无完美是一种修行,过度追求完美是一种“我执”,接纳污秽、肮脏、邪恶、小人是一种高度

目录 前言: 一、天地以万物为刍狗,站在大自然的视角没有善恶 1.1 天地以万物为刍狗,在自然面前,一切生命都平等 1.2 食物链是一个闭环系统,无所谓善恶 1.3 在大自然中,人类眼中的“美丽”都诞生于“污…

Python(八十八)函数的参数传递

❤️ 专栏简介:本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中,我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 :本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

C++设计模式-单件(Singleton)

目录 C设计模式-单件(Singleton) 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-单件(Singleton) 一、意图 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 二、适用性 当类只能有一…

RK3588开发笔记(一):基于方案商提供的宿主机交叉编译Qt5.12.10

若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/133618273 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

最强中间件!Kafka快速入门(Kafka理论+SpringBoot集成Kafka实践)

自媒体文章上下架 需求分析 媒体端下架文章同时app端也下架文章的实现可以通过feign去调用,但这种实现耦合度太高,这里使用MQ进行解耦 自媒体端一旦上下架文章就发送消息给MQ,文章微服务在去读取消息根据消息内容上下架文章 MQ还可以流量削…

SSM - Springboot - MyBatis-Plus 全栈体系(二十二)

第四章 SpringMVC 五、SpringMVC 其他扩展 1. 全局异常处理机制 1.1 异常处理两种方式 开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。…

嵌入式学习笔记(47)Nand的常见操作及流程分析

9.3.1坏块检查 (1)Flash使用之前一定要先统一擦除(擦除的单元是块)。Flash类设备擦除后里边全是1,所以擦干净之后读出来的值是0xFF (2)检查坏块的思路就是:先擦除,然后将整个块读出来,依次检测各字节是否…

【Java项目推荐之黑马头条】CSDN中的定时发布知道吧,你项目中的定时发布是如何实现的?

延迟队列精准发布文章 文章定时发布 延迟任务概述 什么是延迟任务 定时任务:有固定周期的,有明确的触发时间延迟队列:没有固定的开始时间,它常常是由一个事件触发的,而在这个事件触发之后的一段时间内触发另一个事…

起号1个月后,我分析了一些AI数字人项目的红利期和优缺点

本期是赤辰第33期AI项目教程,底部准备了9月粉丝福利,可以免费领取。hi,同学们,AI的应用在各场景都已经呈井喷态势,好比就连近期的杭州亚运会开幕式都采用了数字人火炬手,AI技术的发展不断刷新着我们的脑洞上…

数据科学家的编程语言

数据科学家的编程语言 在今天有256种编程语言可供选择,选择要学习的语言可能会令人不知所措和困难。有些语言更适用于构建游戏,而有些更适用于软件工程,还有一些更适用于数据科学。 编程语言的类型 低级编程语言是计算机用来执行操作的最容…