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

 Gin集成Casbin进行访问权限控制 Gin集成 Casbin 开源 第1张

Casbin是什么

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

其功能有:

  • 支持自定义请求的格式,默认的请求格式为{subject, object, action}。.
  • 具有访问控制模型model和策略policy两个核心概念。
  • 支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。
  • 支持内置的超级用户 例如:root或administrator。超级用户可以执行任何操作而无需显式的权限声明。
  • 支持多种内置的操作符,如 keyMatch,方便对路径式的资源进行管理,如 /foo/bar可以映射到 /foo*

Casbin的工作原理

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

  • Policy:定义权限的规则
  • Effect:定义组合了多个Policy之后的结果
  • Request:访问请求
  • Matcher:判断Request是否满足Policy

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

Casbin的核心概念

Model

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

  • Model CONF 至少应包含四个部分: [request_definition], [policy_definition], [policy_effect], [matchers]。
  • 如果 model 使用 RBAC, 还需要添加[role_definition]部分。
  • Model CONF 文件可以包含注释。注释以 # 开头, # 会注释该行剩余部分。

比如:

  1. #Request定义
  2. [request_definition]
  3. r=sub,obj,act
  4. #策略定义
  5. [policy_definition]
  6. p=sub,obj,act
  7. #角色定义
  8. [role_definition]
  9. g=_,_
  10. [policy_effect]
  11. e=some(where(p.eft==allow))
  12. #匹配器定义
  13. [matchers]
  14. m=g(r.sub,p.sub)&&r.obj==p.obj&&r.act==p.act
  • request_definition:用于request的定义,它明确了e.Enforce(...)函数中参数的定义,sub, obj, act 表示经典三元组: 访问实体 (Subject),访问资源 (Object) 和访问方法 (Action)。
  • policy_definition:用于policy的定义,每条规则通常以形如p的policy type开头,比如p,joker,data1,read就是一条joker具有data1读权限的规则。
  • role_definition:是RBAC角色继承关系的定义。g 是一个 RBAC系统,_, _表示角色继承关系的前项和后项,即前项继承后项角色的权限。
  • policy_effect:是对policy生效范围的定义,它对request的决策结果进行统一的决策,比如e = some(where (p.eft == allow))就表示如果存在任意一个决策结果为allow的匹配规则,则最终决策结果为allow。p.eft 表示策略规则的决策结果,可以为allow 或者deny,当不指定规则的决策结果时,取默认值allow 。
  • matchers:定义了策略匹配者。匹配者是一组表达式,它定义了如何根据请求来匹配策略规则

Policy

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

比如:

  1. p,alice,data1,read
  2. p,bob,data2,write
  3. p,data2_admin,data2,read
  4. p,data2_admin,data2,write
  5. g,alice,data2_admin

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

实践

创建项目

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

项目里的目录结构如下:

  1. ├─configs#配置文件
  2. ├─global#全局变量
  3. ├─internal#内部模块
  4. │├─dao#数据处理模块
  5. │├─middleware#中间件
  6. │├─model#模型层
  7. │├─router#路由
  8. ││└─api
  9. ││└─v1#视图
  10. │└─service#业务逻辑层
  11. └─pkg#内部模块包
  12. ├─app#应用包
  13. ├─errcode#错误代码包
  14. └─setting#配置包

下载依赖包,如下:

  1. goget-ugithub.com/gin-gonic/gin
  2. #Go语言casbin的依赖包
  3. gogetgithub.com/casbin/casbin
  4. #gorm适配器依赖包
  5. gogetgithub.com/casbin/gorm-adapter
  6. #mysql驱动依赖
  7. gogetgithub.com/go-sql-driver/mysql
  8. #gorm包
  9. gogetgithub.com/jinzhu/gorm

创建数据库,如下:

  1. CREATEDATABASE`casbin_test`DEFAULTCHARACTERSETutf8;
  2. GRANTAlter,AlterRoutine,Create,CreateRoutine,CreateTemporaryTables,CreateView,Delete,Drop,Event,Execute,Index,Insert,LockTables,References,Select,ShowView,Trigger,UpdateON`casbin\_test`.*TO`ops`@`%`;
  3. FLUSHPRIVILEGES;
  4. DROPTABLEIFEXIST`casbin_rule`;
  5. CREATETABLE`casbin_rule`(
  6. `p_type`varchar(100)DEFAULTNULLCOMMENT'规则类型',
  7. `v0`varchar(100)DEFAULTNULLCOMMENT'角色ID',
  8. `v1`varchar(100)DEFAULTNULLCOMMENT'api路径',
  9. `v2`varchar(100)DEFAULTNULLCOMMENT'api访问方法',
  10. `v3`varchar(100)DEFAULTNULL,
  11. `v4`varchar(100)DEFAULTNULL,
  12. `v5`varchar(100)DEFAULTNULL
  13. )ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='权限规则表';
  14. /*插入操作casbinapi的权限规则*/
  15. INSERTINTO`casbin_rule`(`p_type`,`v0`,`v1`,`v2`)VALUES('p','admin','/api/v1/casbin','POST');
  16. INSERTINTO`casbin_rule`(`p_type`,`v0`,`v1`,`v2`)VALUES('p','admin','/api/v1/casbin/list','GET');

代码开发

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

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

  1. [request_definition]
  2. r=sub,obj,act
  3. [policy_definition]
  4. p=sub,obj,act
  5. [role_definition]
  6. g=_,_
  7. [policy_effect]
  8. e=some(where(p.eft==allow))
  9. [matchers]
  10. m=r.sub==p.sub&&ParamsMatch(r.obj,p.obj)&&r.act==p.act

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

  1. typeCasbinModelstruct{
  2. PTypestring`json:"p_type"gorm:"column:p_type"description:"策略类型"`
  3. RoleIdstring`json:"role_id"gorm:"column:v0"description:"角色ID"`
  4. Pathstring`json:"path"gorm:"column:v1"description:"api路径"`
  5. Methodstring`json:"method"gorm:"column:v2"description:"访问方法"`
  6. }
  7. func(c*CasbinModel)TableName()string{
  8. return"casbin_rule"
  9. }
  10. func(c*CasbinModel)Create(db*gorm.DB)error{
  11. e:=Casbin()
  12. ifsuccess:=e.AddPolicy(c.RoleId,c.Path,c.Method);success==false{
  13. returnerrors.New("存在相同的API,添加失败")
  14. }
  15. returnnil
  16. }
  17. func(c*CasbinModel)Update(db*gorm.DB,valuesinterface{})error{
  18. iferr:=db.Model(c).Where("v1=?ANDv2=?",c.Path,c.Method).Update(values).Error;err!=nil{
  19. returnerr
  20. }
  21. returnnil
  22. }
  23. func(c*CasbinModel)List(db*gorm.DB)[][]string{
  24. e:=Casbin()
  25. policy:=e.GetFilteredPolicy(0,c.RoleId)
  26. returnpolicy
  27. }
  28. //@function:Casbin
  29. //@description:持久化到数据库引入自定义规则
  30. //@return:*casbin.Enforcer
  31. funcCasbin()*casbin.Enforcer{
  32. s:=fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=%s&parseTime=%t&loc=Local",
  33. global.DatabaseSetting.Username,
  34. global.DatabaseSetting.Password,
  35. global.DatabaseSetting.Host,
  36. global.DatabaseSetting.DBName,
  37. global.DatabaseSetting.Charset,
  38. global.DatabaseSetting.ParseTime,
  39. )
  40. db,_:=gorm.Open(global.DatabaseSetting.DBType,s)
  41. adapter:=gormadapter.NewAdapterByDB(db)
  42. enforcer:=casbin.NewEnforcer(global.CasbinSetting.ModelPath,adapter)
  43. enforcer.AddFunction("ParamsMatch",ParamsMatchFunc)
  44. _=enforcer.LoadPolicy()
  45. returnenforcer
  46. }
  47. //@function:ParamsMatch
  48. //@description:自定义规则函数
  49. //@param:fullNameKey1string,key2string
  50. //@return:bool
  51. funcParamsMatch(fullNameKey1string,key2string)bool{
  52. key1:=strings.Split(fullNameKey1,"?")[0]
  53. //剥离路径后再使用casbin的keyMatch2
  54. returnutil.KeyMatch2(key1,key2)
  55. }
  56. //@function:ParamsMatchFunc
  57. //@description:自定义规则函数
  58. //@param:args...interface{}
  59. //@return:interface{},error
  60. funcParamsMatchFunc(args...interface{})(interface{},error){
  61. name1:=args[0].(string)
  62. name2:=args[1].(string)
  63. returnParamsMatch(name1,name2),nil
  64. }

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

  1. func(d*Dao)CasbinCreate(roleIdstring,path,methodstring)error{
  2. cm:=model.CasbinModel{
  3. PType:"p",
  4. RoleId:roleId,
  5. Path:path,
  6. Method:method,
  7. }
  8. returncm.Create(d.engine)
  9. }
  10. func(d*Dao)CasbinList(roleIDstring)[][]string{
  11. cm:=model.CasbinModel{RoleId:roleID}
  12. returncm.List(d.engine)
  13. }

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

  1. typeCasbinInfostruct{
  2. Pathstring`json:"path"form:"path"`
  3. Methodstring`json:"method"form:"method"`
  4. }
  5. typeCasbinCreateRequeststruct{
  6. RoleIdstring`json:"role_id"form:"role_id"description:"角色ID"`
  7. CasbinInfos[]CasbinInfo`json:"casbin_infos"description:"权限模型列表"`
  8. }
  9. typeCasbinListResponsestruct{
  10. List[]CasbinInfo`json:"list"form:"list"`
  11. }
  12. typeCasbinListRequeststruct{
  13. RoleIDstring`json:"role_id"form:"role_id"`
  14. }
  15. func(sService)CasbinCreate(param*CasbinCreateRequest)error{
  16. for_,v:=rangeparam.CasbinInfos{
  17. err:=s.dao.CasbinCreate(param.RoleId,v.Path,v.Method)
  18. iferr!=nil{
  19. returnerr
  20. }
  21. }
  22. returnnil
  23. }
  24. func(sService)CasbinList(param*CasbinListRequest)[][]string{
  25. returns.dao.CasbinList(param.RoleID)
  26. }

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

  1. typeCasbinstruct{
  2. }
  3. funcNewCasbin()Casbin{
  4. returnCasbin{}
  5. }
  6. //Creategodoc
  7. //@Summary新增权限
  8. //@Description新增权限
  9. //@Tags权限管理
  10. //@Producejson
  11. //@SecurityApiKeyAuth
  12. //@Parambodybodyservice.CasbinCreateRequesttrue"body"
  13. //@Success200{object}string"成功"
  14. //@Failure400{object}errcode.Error"请求错误"
  15. //@Failure500{object}errcode.Error"内部错误"
  16. //@Router/api/v1/casbin[post]
  17. func(cCasbin)Create(ctx*gin.Context){
  18. param:=service.CasbinCreateRequest{}
  19. response:=app.NewResponse(ctx)
  20. valid,errors:=app.BindAndValid(ctx,&param)
  21. if!valid{
  22. log.Printf("app.BindAndValiderrs:%v",errors)
  23. errRsp:=errcode.InvalidParams.WithDetails(errors.Errors()...)
  24. response.ToErrorResponse(errRsp)
  25. return
  26. }
  27. //进行插入操作
  28. svc:=service.NewService(ctx)
  29. err:=svc.CasbinCreate(&param)
  30. iferr!=nil{
  31. log.Printf("svc.CasbinCreateerr:%v",err)
  32. response.ToErrorResponse(errcode.ErrorCasbinCreateFail)
  33. }
  34. response.ToResponse(gin.H{})
  35. return
  36. }
  37. //Listgodoc
  38. //@Summary获取权限列表
  39. //@Producejson
  40. //@Tags权限管理
  41. //@SecurityApiKeyAuth
  42. //@Paramdatabodyservice.CasbinListRequesttrue"角色ID"
  43. //@Success200{object}service.CasbinListResponse"成功"
  44. //@Failure400{object}errcode.Error"请求错误"
  45. //@Failure500{object}errcode.Error"内部错误"
  46. //@Router/api/v1/casbin/list[post]
  47. func(cCasbin)List(ctx*gin.Context){
  48. param:=service.CasbinListRequest{}
  49. response:=app.NewResponse(ctx)
  50. valid,errors:=app.BindAndValid(ctx,&param)
  51. if!valid{
  52. log.Printf("app.BindAndValiderrs:%v",errors)
  53. errRsp:=errcode.InvalidParams.WithDetails(errors.Errors()...)
  54. response.ToErrorResponse(errRsp)
  55. return
  56. }
  57. //业务逻辑处理
  58. svc:=service.NewService(ctx)
  59. casbins:=svc.CasbinList(&param)
  60. varrespList[]service.CasbinInfo
  61. for_,host:=rangecasbins{
  62. respList=append(respList,service.CasbinInfo{
  63. Path:host[1],
  64. Method:host[2],
  65. })
  66. }
  67. response.ToResponseList(respList,0)
  68. return
  69. }

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

  1. p,alice,data1,read
  2. p,bob,data2,write
  3. p,data2_admin,data2,read
  4. p,data2_admin,data2,write
  5. g,alice,data2_admin
0

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

  1. p,alice,data1,read
  2. p,bob,data2,write
  3. p,data2_admin,data2,read
  4. p,data2_admin,data2,write
  5. g,alice,data2_admin
1

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

  1. p,alice,data1,read
  2. p,bob,data2,write
  3. p,data2_admin,data2,read
  4. p,data2_admin,data2,write
  5. g,alice,data2_admin
2

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

验证

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

 Gin集成Casbin进行访问权限控制 Gin集成 Casbin 开源 第2张

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

 Gin集成Casbin进行访问权限控制 Gin集成 Casbin 开源 第3张

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

 Gin集成Casbin进行访问权限控制 Gin集成 Casbin 开源 第4张

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

 Gin集成Casbin进行访问权限控制 Gin集成 Casbin 开源 第5张

参考文档:

[1] https://casbin.org/

[2] https://casbin.org/docs/zh-CN/overview

[3] https://gitee.com/coolops/casbin_test.git

 Gin集成Casbin进行访问权限控制 Gin集成 Casbin 开源 第6张

转载请说明出处
知优网 » Gin集成Casbin进行访问权限控制

发表评论

您需要后才能发表评论