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

文章目录

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

0. 背景

Casbin是用于Golang项目的功能强大且高效的开源访问控制库。

强大通用也意味着概念和配置较多,具体到实际应用(以Gin Web框架开发)需要解决以下问题:

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

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

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

1. 准备工作

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

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 = g(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结构和数据库表如下

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组只能查询用户

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 user

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

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"
)

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 = g(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中示范数据,预期输出

json 复制代码
[{
	"username": "leo",
	"roleNames": ["admin"]
}, {
	"username": "leo2",
	"roleNames": ["user"]
}]

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

go 复制代码
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 角色组中添加用户

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

2.2.3 角色组中删除用户

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

2.3 角色组权限的增删改查

2.3.1 获取所有角色组权限

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

json 复制代码
[{
	"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 创建角色组权限

go 复制代码
// 创建角色组权限, 已有的会忽略
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 修改角色组权限

go 复制代码
// 修改角色组权限
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 删除角色组权限

go 复制代码
// 删除角色组权限
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()
}

2.3.5 验证用户权限

go 复制代码
// 验证用户权限
func (c *CasbinService) CanAccess(username, url, method string) (ok bool, err error) {
	return c.enforcer.Enforce(username, url, method)
}

3. 测试以及完整代码

3.1 casbin_service.go

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     |        |    |    |    |
+-------+-------+-----------+--------+----+----+----+
| g     | leo3  | user      |        |    |    |    |
+-------+-------+-----------+--------+----+----+----+
*/
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 = g(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
}

// 验证用户权限
func (c *CasbinService) CanAccess(username, url, method string) (ok bool, err error) {
	return c.enforcer.Enforce(username, url, method)
}

3.2 casbin_service_test.go

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权限验证

相关推荐
_.Switch13 分钟前
Python 自动化运维持续优化与性能调优
运维·开发语言·python·缓存·自动化·运维开发
徐*红13 分钟前
java 线程池
java·开发语言
尚学教辅学习资料13 分钟前
基于SSM的养老院管理系统+LW示例参考
java·开发语言·java毕设·养老院
2401_8576363913 分钟前
计算机课程管理平台:Spring Boot与工程认证的结合
java·spring boot·后端
1 9 J15 分钟前
Java 上机实践4(类与对象)
java·开发语言·算法
Code apprenticeship16 分钟前
Java面试题(2)
java·开发语言
J不A秃V头A19 分钟前
Python爬虫:获取国家货币编码、货币名称
开发语言·爬虫·python
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
SRY122404193 小时前
javaSE面试题
java·开发语言·面试
__AtYou__3 小时前
Golang | Leetcode Golang题解之第557题反转字符串中的单词III
leetcode·golang·题解