文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何在Go中使用Casbin进行访问控制

2024-04-02 19:55

关注

Casbin是什么

Casbin是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型,Casbin只负责访问控制。

其功能有:

Casbin的工作原理

在 Casbin 中, 访问控制模型被抽象为基于 **PERM **(Policy, Effect, Request, Matcher) [策略,效果,请求,匹配器]的一个文件。

首先会定义一堆Policy,让后通过Matcher来判断Request和Policy是否匹配,然后通过Effect来判断匹配结果是Allow还是Deny。

Casbin的核心概念

Model

Model是Casbin的具体访问模型,其主要以文件的形式出现,该文件常常以.conf最为后缀。

比如:

# Request定义
[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) && r.obj == p.obj && r.act == p.act

Policy

Policy主要表示访问控制关于角色、资源、行为的具体映射关系。

比如:

p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin

它的关系规则很简单,主要是选择什么方式来存储规则,目前官方提供csv文件存储和通过adapter适配器从其他存储系统中加载配置文件,比如MySQL, PostgreSQL, SQL Server, SQLite3,MongoDB,Redis,Cassandra DB等。

实践

创建项目

首先创建一个项目,叫casbin_test。

项目里的目录结构如下:

├─configs         # 配置文件
├─global				  # 全局变量
├─internal        # 内部模块
│  ├─dao					# 数据处理模块
│  ├─middleware   # 中间件
│  ├─model        # 模型层
│  ├─router       # 路由
│  │  └─api
│  │      └─v1    # 视图
│  └─service      # 业务逻辑层
└─pkg             # 内部模块包
    ├─app         # 应用包
    ├─errcode     # 错误代码包
    └─setting     # 配置包

下载依赖包,如下:

go get -u github.com/gin-gonic/gin
# Go语言casbin的依赖包
go get github.com/casbin/casbin
# gorm 适配器依赖包
go get github.com/casbin/gorm-adapter
# mysql驱动依赖
go get github.com/go-sql-driver/mysql
# gorm 包
go get github.com/jinzhu/gorm

创建数据库,如下:

CREATE DATABASE `casbin_test` DEFAULT CHARACTER SET utf8;
GRANT Alter, Alter Routine, Create, Create Routine, Create Temporary Tables, Create View, Delete, Drop, Event, Execute, Index, Insert, Lock Tables, References, Select, Show View, Trigger, Update ON `casbin\_test`.* TO `ops`@`%`;
FLUSH PRIVILEGES;
DROP TABLE IF EXIST `casbin_rule`;
CREATE TABLE `casbin_rule` (
  `p_type` varchar(100) DEFAULT NULL COMMENT '规则类型',
  `v0` varchar(100) DEFAULT NULL COMMENT '角色ID',
  `v1` varchar(100) DEFAULT NULL COMMENT 'api路径',
  `v2` varchar(100) DEFAULT NULL COMMENT 'api访问方法',
  `v3` varchar(100) DEFAULT NULL,
  `v4` varchar(100) DEFAULT NULL,
  `v5` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='权限规则表';

INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`) VALUES ('p', 'admin', '/api/v1/casbin', 'POST');
INSERT INTO `casbin_rule`(`p_type`, `v0`, `v1`, `v2`) VALUES ('p', 'admin', '/api/v1/casbin/list', 'GET');

代码开发

由于代码比较多,这里就不贴全部代码了,全部代码已经放在gitee仓库,可以自行阅读,这些仅仅贴部分关键代码。

(1)首先在configs目录下创建rbac_model.conf文件,写入如下代码:

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

(2)在internal/model目录下,创建casbin.go文件,写入如下代码:

type CasbinModel struct {
	PType  string `json:"p_type" gorm:"column:p_type" description:"策略类型"`
	RoleId string `json:"role_id" gorm:"column:v0" description:"角色ID"`
	Path   string `json:"path" gorm:"column:v1" description:"api路径"`
	Method string `json:"method" gorm:"column:v2" description:"访问方法"`
}

func (c *CasbinModel) TableName() string {
	return "casbin_rule"
}

func (c *CasbinModel) Create(db *gorm.DB) error {
	e := Casbin()
	if success := e.AddPolicy(c.RoleId,c.Path,c.Method); success == false {
		return errors.New("存在相同的API,添加失败")
	}
	return nil
}

func (c *CasbinModel) Update(db *gorm.DB, values interface{}) error {
	if err := db.Model(c).Where("v1 = ? AND v2 = ?", c.Path, c.Method).Update(values).Error; err != nil {
		return err
	}
	return nil
}

func (c *CasbinModel) List(db *gorm.DB) [][]string {
	e := Casbin()
	policy := e.GetFilteredPolicy(0, c.RoleId)
	return policy
}

//@function: Casbin
//@description: 持久化到数据库  引入自定义规则
//@return: *casbin.Enforcer
func Casbin() *casbin.Enforcer {
	s := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s&parseTime=%t&loc=Local",
		global.DatabaseSetting.Username,
		global.DatabaseSetting.Password,
		global.DatabaseSetting.Host,
		global.DatabaseSetting.DBName,
		global.DatabaseSetting.Charset,
		global.DatabaseSetting.ParseTime,
	)
	db, _ := gorm.Open(global.DatabaseSetting.DBType, s)

	adapter := gormadapter.NewAdapterByDB(db)
	enforcer := casbin.NewEnforcer(global.CasbinSetting.ModelPath, adapter)
	enforcer.AddFunction("ParamsMatch", ParamsMatchFunc)
	_ = enforcer.LoadPolicy()
	return enforcer
}

//@function: ParamsMatch
//@description: 自定义规则函数
//@param: fullNameKey1 string, key2 string
//@return: bool
func ParamsMatch(fullNameKey1 string, key2 string) bool {
	key1 := strings.Split(fullNameKey1, "?")[0]
	// 剥离路径后再使用casbin的keyMatch2
	return util.KeyMatch2(key1, key2)
}

//@function: ParamsMatchFunc
//@description: 自定义规则函数
//@param: args ...interface{}
//@return: interface{}, error
func ParamsMatchFunc(args ...interface{}) (interface{}, error) {
	name1 := args[0].(string)
	name2 := args[1].(string)

	return ParamsMatch(name1, name2), nil
}

(3)在internal/dao目录下创建casbin.go,写入如下代码:

func (d *Dao) CasbinCreate(roleId string, path, method string) error {
	cm := model.CasbinModel{
		PType:  "p",
		RoleId: roleId,
		Path:   path,
		Method: method,
	}
	return cm.Create(d.engine)
}

func (d *Dao) CasbinList(roleID string) [][]string {
	cm := model.CasbinModel{RoleId: roleID}
	return cm.List(d.engine)
}

(4)在internal/service目录下创建service.go,写入如下代码:

type CasbinInfo struct {
	Path   string `json:"path" form:"path"`
	Method string `json:"method" form:"method"`
}
type CasbinCreateRequest struct {
	RoleId      string       `json:"role_id" form:"role_id" description:"角色ID"`
	CasbinInfos []CasbinInfo `json:"casbin_infos" description:"权限模型列表"`
}

type CasbinListResponse struct {
	List []CasbinInfo `json:"list" form:"list"`
}

type CasbinListRequest struct {
	RoleID string `json:"role_id" form:"role_id"`
}

func (s Service) CasbinCreate(param *CasbinCreateRequest) error {
	for _, v := range param.CasbinInfos {
		err := s.dao.CasbinCreate(param.RoleId, v.Path, v.Method)
		if err != nil {
			return err
		}
	}
	return nil
}


func (s Service) CasbinList(param *CasbinListRequest) [][]string {
	return s.dao.CasbinList(param.RoleID)
}

(5)在internal/router/api/v1目录下创建casbin.go写入如下代码:

type Casbin struct {
}

func NewCasbin() Casbin {
	return Casbin{}
}

// Create godoc
// @Summary 新增权限
// @Description 新增权限
// @Tags 权限管理
// @Produce json
// @Security ApiKeyAuth
// @Param body body service.CasbinCreateRequest true "body"
// @Success 200 {object} string "成功"
// @Failure 400 {object} errcode.Error "请求错误"
// @Failure 500 {object} errcode.Error "内部错误"
// @Router /api/v1/casbin [post]
func (c Casbin) Create(ctx *gin.Context) {
	param := service.CasbinCreateRequest{}
	response := app.NewResponse(ctx)
	valid, errors := app.BindAndValid(ctx, &param)
	if !valid {
		log.Printf("app.BindAndValid errs: %v", errors)
		errRsp := errcode.InvalidParams.WithDetails(errors.Errors()...)
		response.ToErrorResponse(errRsp)
		return
	}

	// 进行插入操作
	svc := service.NewService(ctx)
	err := svc.CasbinCreate(&param)
	if err != nil {
		log.Printf("svc.CasbinCreate err: %v", err)
		response.ToErrorResponse(errcode.ErrorCasbinCreateFail)
	}
	response.ToResponse(gin.H{})
	return
}


// List godoc
// @Summary 获取权限列表
// @Produce json
// @Tags 权限管理
// @Security ApiKeyAuth
// @Param data body service.CasbinListRequest true "角色ID"
// @Success 200 {object} service.CasbinListResponse "成功"
// @Failure 400 {object} errcode.Error "请求错误"
// @Failure 500 {object} errcode.Error "内部错误"
// @Router /api/v1/casbin/list [post]
func (c Casbin) List(ctx *gin.Context) {
	param := service.CasbinListRequest{}
	response := app.NewResponse(ctx)
	valid, errors := app.BindAndValid(ctx, &param)
	if !valid {
		log.Printf("app.BindAndValid errs: %v", errors)
		errRsp := errcode.InvalidParams.WithDetails(errors.Errors()...)
		response.ToErrorResponse(errRsp)
		return
	}
	// 业务逻辑处理
	svc := service.NewService(ctx)
	casbins := svc.CasbinList(&param)
	var respList []service.CasbinInfo
	for _, host := range casbins {
		respList = append(respList, service.CasbinInfo{
			Path:   host[1],
			Method: host[2],
		})
	}
	response.ToResponseList(respList, 0)
	return
}

再在该目录下创建一个test.go文件,用于测试,代码如下:

type Test struct {
}

func NewTest() Test {
	return Test{}
}

func (t Test) Get(ctx *gin.Context) {
	log.Println("Hello 接收到GET请求..")
	response := app.NewResponse(ctx)
	response.ToResponse("接收GET请求成功")
}

(6)在internal/middleware目录下创建casbin_handler.go写入如下代码:

func CasbinHandler() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		response := app.NewResponse(ctx)
		// 获取请求的URI
		obj := ctx.Request.URL.RequestURI()
		// 获取请求方法
		act := ctx.Request.Method
		// 获取用户的角色
		sub := "admin"
		e := model.Casbin()
		fmt.Println(obj, act, sub)
		// 判断策略中是否存在
		success := e.Enforce(sub, obj, act)
		if success {
			log.Println("恭喜您,权限验证通过")
			ctx.Next()
		} else {
			log.Printf("e.Enforce err: %s", "很遗憾,权限验证没有通过")
			response.ToErrorResponse(errcode.UnauthorizedAuthFail)
			ctx.Abort()
			return
		}
	}
}

(7)在internal/router目录下创建router.go,定义路由,代码如下:

func NewRouter() *gin.Engine {
	r := gin.New()
	r.Use(gin.Logger())
	r.Use(gin.Recovery())
	casbin := v1.NewCasbin()
	test := v1.NewTest()
	apiv1 := r.Group("/api/v1")
	apiv1.Use(middleware.CasbinHandler())
	{
		// 测试路由
		apiv1.GET("/hello", test.Get)

		// 权限策略管理
		apiv1.POST("/casbin", casbin.Create)
		apiv1.POST("/casbin/list", casbin.List)
	}
	return r
}

最后就启动项目进行测试。

验证

(1)首先访问测试路径,当前情况下没在权限表里,如下: 

(2)将测试路径添加到权限列表,如下: 

 (3)然后再次访问测试路径,如下: 

 并且从日志上也可以看到,如下: 

到此这篇关于如何在Go中使用Casbin进行访问控制的文章就介绍到这了,更多相关Go Casbin访问控制内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯