Go语言 gin框架集成Casbin实现访问权限控制

1. Casbin是什么?

Casbin是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。因此Casbin不能做身份验证, 最佳的实践是只负责访问控制

1.1 Casbin的model

Casbin 中, 访问控制模型被抽象为基于 PERM (Policy, Effect, Request, Matcher) 的一个文件,这个文件的具体呈现是一个以 .conf 作为后缀的文件

example :

rbac_model.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

对于上面配置文件简单的理解是:


1.1.1 [request_definition]

r = sub, obj, act :定义请求由三部分组成 访问用户的用户 Subject , 访问的资源 Object 访问的动作 Action

1.1.2 [policy_definition]

p = sub, obj, act : 定策略的格式 , 参数的基本意思和定义请求的相同 ,定义好了策略格式,那么对于策略(Policy)的具体描述可以存放在一个以 .csv 作为后缀的文件中

example :

rbac_Policy_example.csv

g, coder, root
g, zhangsan coder
p, root,api/v1/ping,GET
p, coder,api/v1/pong,GET
g, lisi, manager
p, manager, api/v1/user,POST

上面的rbac策略中我们定义了三条策略和三个用户组,我们来看一下这些策略都有啥作用

  1. coder是root的角色
  2. zhangsan是coder的角色
  3. root 可以访问 api/v1/ping 资源 通过GET动作,那么coder , zhangsan也可以访问
  4. coder可以访问 api/v1/pong 资源 通过GET动作,zhangsan也能访问
  5. lisi是manager的角色
  6. manager可以访问 api/v1/user资源通过POST动作,lisi也可以访问
1.1.3 [role_definition]

**g = _, _ ** : 是RBAC角色继承关系的定义 ,此处的 _, _ 表示 前项继承后项角色的权限

1.1.4 [policy_effect]

e = some(where (p.eft == allow)) : 表示任意一条Policy策略满足那么结果就为allow

1.1.5 [matchers]

m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act : 定义了策略匹配者。匹配者是一组表达式。它定义了如何根据请求来匹配策略规则,匹配表达式的写法比较灵活根据具体需求来编写即可.
而此处的表达式意思是 ,检测用户角色 && 检测用户访问的资源 &&检测用户的动作 (&&表示并且关系,当然也有其他逻辑运算符 ||,!等)

1.2 Casbin的Policy

Policy 主要表示访问控制关于角色,资源,行为的具体映射关系这比较好处理,但是这种映射关系怎么存储就值得考虑了

1.2.1 csv 文件存储
访问控制模型Model 文件Policy 文件
ACLbasic_model.confbasic_policy.csv
具有超级用户的ACLbasic_with_root_model.confbasic_policy.csv
没有用户的ACLbasic_without_users_model.confbasic_without_users_policy.csv
没有资源的ACLbasic_without_resources_model.confbasic_without_resources_policy.csv
RBACrbac_model.confrbac_policy.csv
支持资源角色的RBACrbac_with_resource_roles_model.confrbac_with_resource_roles_policy.csv
支持域/租户的RBACrbac_with_domains_model.confrbac_with_domains_policy.csv
ABACabac_model.conf
RESTfulkeymatch_model.confkeymatch_policy.csv
拒绝优先rbac_with_not_deny_model.confrbac_with_deny_policy.csv
Allow-and-denyrbac_with_deny_model.confrbac_with_deny_policy.csv
Prioritypriority_model.confpriority_policy.csv
1.2.2 适配器存储

casbin的适配器 adapter 可以从存储中加载策略规则,也可将策略规则保存到不同的存储系统中

支持如: MySQL, PostgreSQL, SQL Server, SQLite3,MongoDB,Redis,Cassandra DB等等存储系统

适配器类型作者自动保存描述
File Adapter (内置)FileCasbinFor .CSV (Comma-Separated Values) files
Filtered File Adapter (内置)File@faceless-saintFor .CSV (Comma-Separated Values) files with policy subset loading support
SQL AdapterSQL@Blank-XuMySQL, PostgreSQL, SQL Server, SQLite3 are supported in master branch and Oracle is supported in oracle branch by database/sql
Xorm AdapterORMCasbinMySQL, PostgreSQL, TiDB, SQLite, SQL Server, Oracle are supported by Xorm
Gorm AdapterORMCasbinMySQL, PostgreSQL, Sqlite3, SQL Server are supported by Gorm
Beego ORM AdapterORMCasbinMySQL, PostgreSQL, Sqlite3 are supported by Beego ORM
SQLX AdapterORM@memweyMySQL, PostgreSQL, SQLite, Oracle are supported by SQLX
Sqlx AdapterSQL@Blank-XuMySQL, PostgreSQL, SQL Server, SQLite3 are supported in master branch and Oracle is supported in oracle branch by sqlx
GF ORM AdapterORM@vance-liuMySQL, SQLite, PostgreSQL, Oracle, SQL Server are supported by GF ORM
Filtered PostgreSQL AdapterSQLCasbinFor PostgreSQL
PostgreSQL AdapterSQL@cychiuaeFor PostgreSQL
PostgreSQL Adapter (Archived)SQLGoingFor PostgreSQL
RQLite AdapterSQLEDOMO SystemsFor RQLite
MongoDB AdapterNoSQLCasbinFor MongoDB based on MongoDB driver for Go
MongoDB AdapterNoSQLTitan DCFor MongoDB based on MongoDB Go driver
RethinkDB AdapterNoSQL@adityapandey9For RethinkDB
Cassandra AdapterNoSQLCasbinFor Apache Cassandra DB
DynamoDB AdapterNoSQLHOOQFor Amazon DynamoDB
DynacasbinNoSQLNewbMiaoFor Amazon DynamoDB
ArangoDB AdapterNoSQL@adamwasilaFor ArangoDB
Amazon S3 AdapterCloudSolutoFor Minio and Amazon S3
Azure Cosmos DB AdapterCloud@spacycoderFor Microsoft Azure Cosmos DB
GCP Datastore AdapterCloudLivingPacketsFor Google Cloud Platform Datastore
GCP Firestore AdapterCloud@reedomFor Google Cloud Platform Firestore
Consul AdapterKV store@ankitm123For HashiCorp Consul
Redis AdapterKV storeCasbinFor Redis
Etcd AdapterKV store@sebastianliuFor etcd
BoltDB AdapterKV store@spezaFor Bolt
Bolt AdapterKV store@wirepairFor Bolt
BadgerDB AdapterKV store@initsFor BadgerDB
Protobuf AdapterStreamCasbinFor Google Protocol Buffers
JSON AdapterStringCasbinFor JSON
String AdapterString@qiangmzsxFor String

2. gin集成Casbin实现RESTful接口访问控制

2.1 go mod 构建项目
# 新建个叫做ginCasbin的gomod项目(项目名自定义)
go mod init GinCasbin
2.2 安装依赖包
# 安装依赖包
# 安装gin框架
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
# 高性能缓存BigCache
go get github.com/allegro/bigcache/v2
2.3 目录规划说明
├─app # 业务目录
│  ├─api  ## 存放api的目录(暂时不用)
│  ├─model ## 存放实体的目录(暂时不用)
│  └─service ## 存放业务代码的目录(暂时不用)
├─config # 存放配置文件的目录
├─middleware # 存放中间件的目录
├─routers # 存放路由的目录
└─utils # 常用工具组件目录
    ├─ACS ## 存放访问控制执行器目录
    ├─APIResponse ##  存放API统一响应函数目录
    ├─Cache ## 缓存工具目录
    └─DB ## 数据连接文件目录
├─go.mod
├─go.sum
├─main.go # 项目入口文件

2.4 项目代码开发
2.4.1 工具组件开发
# 进入utils目录
cd utils

DB/mysql.go

package DB

import (
	"fmt"
	"github.com/jinzhu/gorm"
)
import _ "github.com/go-sql-driver/mysql"

var (
	Mysql *gorm.DB
)

func init() {
	var err error
	dsn := "root:root@(127.0.0.1:3306)/xz_boss?charset=utf8&parseTime=True&loc=Local"
	Mysql, err = gorm.Open("mysql", dsn)
	if err != nil {
		fmt.Println("connect DB error")
		panic(err)
	}
}

ACS/enforcer.go

package ACS

import (
	"GinCasbin/utils/DB"
	"github.com/casbin/casbin"
	"github.com/casbin/gorm-adapter"
)

var Enforcer *casbin.Enforcer

func init() {
	// mysql 适配器
	adapter := gormadapter.NewAdapterByDB(DB.Mysql)
	// 通过mysql适配器新建一个enforcer
	Enforcer = casbin.NewEnforcer("config/keymatch2_model.conf", adapter)
	// 日志记录
	Enforcer.EnableLog(true)
}


APIResponse/response.go

package APIResponse

import "github.com/gin-gonic/gin"

type Response struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data"`
}

var C *gin.Context

func Error(message string) {
	if len(message) == 0 {
		message = "fail"
	}
	C.JSON(200, Response{
		Code:    -1,
		Message: message,
		Data:    nil,
	})
}
func Success(data interface{}) {
	C.JSON(200, Response{
		Code:    200,
		Message: "success",
		Data:    data,
	})
}

Cache/big.go

package Cache

import (
	"github.com/allegro/bigcache/v2"
	"time"
)

var GlobalCache *bigcache.BigCache

func init() {
	// 初始化BigCache实例
	GlobalCache, _ = bigcache.NewBigCache(bigcache.DefaultConfig(30 * time.Minute))
}

2.4.2 配置文件

常规项目中配置文件目录中会存放各种配置文件,在这个Demo中仅将casbin的模型文件放在这里

cd ../config

config/keymatch2_model.conf

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
2.4.3 中间件

此处我们编写的一个基于casbin权限控制的中间件

cd ../middleware

middleware/privilege.go

package middleware

import (
	"GinCasbin/utils/ACS"
	"GinCasbin/utils/APIResponse"
	"GinCasbin/utils/Cache"
	"github.com/gin-gonic/gin"
	"log"
)

func Privilege() gin.HandlerFunc {
	return func(c *gin.Context) {
		APIResponse.C = c
		var userName = c.GetHeader("userName")
		if userName == "" {
			APIResponse.Error("header miss userName")
			c.Abort()
			return
		}
		path := c.Request.URL.Path
		method := c.Request.Method
		cacheName := userName + path + method
		// 从缓存中读取&判断
		entry, err := Cache.GlobalCache.Get(cacheName)
		if err == nil && entry != nil {
			if string(entry) == "true" {
				c.Next()
			} else {
				APIResponse.Error("access denied")
				c.Abort()
				return
			}
		} else {
			// 从数据库中读取&判断
			//记录日志
			ACS.Enforcer.EnableLog(true)
			// 加载策略规则
			err := ACS.Enforcer.LoadPolicy()
			if err != nil {
				log.Println("loadPolicy error")
				panic(err)
			}
			// 验证策略规则
			result, err := ACS.Enforcer.EnforceSafe(userName, path, method)
			if err != nil {
				APIResponse.Error("No permission found")
				c.Abort()
				return
			}
			if !result {
				// 添加到缓存中
				Cache.GlobalCache.Set(cacheName, []byte("false"))
				APIResponse.Error("access denied")
				c.Abort()
				return
			} else {
				Cache.GlobalCache.Set(cacheName, []byte("true"))
			}
			c.Next()
		}
	}
}

2.4.4 路由文件
cd ../routers

routers/route.go

package routers

import (
	"GinCasbin/middleware"
	"GinCasbin/utils/ACS"
	"GinCasbin/utils/APIResponse"
	"GinCasbin/utils/Cache"
	"github.com/gin-gonic/gin"
)

var (
	R *gin.Engine
)

func init() {
	R = gin.Default()
	R.NoRoute(func(c *gin.Context) {
		c.JSON(400, gin.H{"code": 400, "message": "Bad Request"})
	})
	api()
}
func api() {
	auth := R.Group("/api")
	{
		// 模拟添加一条Policy策略
		auth.POST("acs", func(c *gin.Context) {
			APIResponse.C = c
			subject := "tom"
			object := "/api/routers"
			action := "POST"
			cacheName := subject + object + action
			result := ACS.Enforcer.AddPolicy(subject, object, action)
			if result {
				// 清除缓存
				_ = Cache.GlobalCache.Delete(cacheName)
				APIResponse.Success("add success")
			} else {
				APIResponse.Error("add fail")
			}
		})
		// 模拟删除一条Policy策略
		auth.DELETE("acs/:id", func(context *gin.Context) {
			APIResponse.C = context
			result := ACS.Enforcer.RemovePolicy("tom", "/api/routers", "POST")
			if result {
				// 清除缓存 代码省略
				APIResponse.Success("delete Policy success")
			} else {
				APIResponse.Error("delete Policy fail")
			}
		})
		// 获取路由列表
		auth.POST("/routers", middleware.Privilege(), func(c *gin.Context) {
			type data struct {
				Method string `json:"method"`
				Path   string `json:"path"`
			}
			var datas []data
			routers := R.Routes()
			for _, v := range routers {
				var temp data
				temp.Method = v.Method
				temp.Path = v.Path
				datas = append(datas, temp)
			}
			APIResponse.C = c
			APIResponse.Success(datas)
			return
		})
	}
	// 定义路由组
	user := R.Group("/api/v1")
	// 使用访问控制中间件
	user.Use(middleware.Privilege())
	{
		user.POST("user", func(c *gin.Context) {
			c.JSON(200, gin.H{"code": 200, "message": "user add success"})
		})
		user.DELETE("user/:id", func(c *gin.Context) {
			id := c.Param("id")
			c.JSON(200, gin.H{"code": 200, "message": "user delete success " + id})
		})
		user.PUT("user/:id", func(c *gin.Context) {
			id := c.Param("id")
			c.JSON(200, gin.H{"code": 200, "message": "user update success " + id})
		})
		user.GET("user/:id", func(c *gin.Context) {
			id := c.Param("id")
			c.JSON(200, gin.H{"code": 200, "message": "user Get success " + id})
		})
	}
}

2.4.5 项目入口文件
cd ..

main.go

package main

import (
	. "GinCasbin/routers"
)

func main() {
	R.Run()
}

2.5 测试访问策略
2.5.1 启动项目
# 运行项目
go run main.go
# gin框架在debug模式下的输出
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /api/acs                  --> GinCasbin/routers.api.func1 (3 handlers)
[GIN-debug] DELETE /api/acs/:id              --> GinCasbin/routers.api.func2 (3 handlers)
[GIN-debug] POST   /api/routers              --> GinCasbin/routers.api.func3 (4 handlers)
[GIN-debug] POST   /api/v1/user              --> GinCasbin/routers.api.func4 (4 handlers)
[GIN-debug] DELETE /api/v1/user/:id          --> GinCasbin/routers.api.func5 (4 handlers)
[GIN-debug] PUT    /api/v1/user/:id          --> GinCasbin/routers.api.func6 (4 handlers)
[GIN-debug] GET    /api/v1/user/:id          --> GinCasbin/routers.api.func7 (4 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

2.5.2 测试casbin访问控制

新开启一个命令行终端

# 访问接口
# 参数缺失
curl -X POST http://127.0.0.1:8080/api/routers
{"code":-1,"message":"header miss userName","data":null}


# 无访问权限
curl -X POST -H "userName:tom" http://127.0.0.1:8080/api/routers
{"code":-1,"message":"access denied","data":null}


# 添加一条规则(代码中是模拟数据)
curl -X POST http://127.0.0.1:8080/api/acs
{"code":200,"message":"success","data":"add success"}

# 再次访问(有访问权限,可以访问)
curl -X POST -H "userName:tom" http://127.0.0.1:8080/api/routers
{
    "code":200,
    "message":"success",
    "data":[
        {
            "method":"POST",
            "path":"/api/acs"
        },
        {
            "method":"POST",
            "path":"/api/routers"
        },
        {
            "method":"POST",
            "path":"/api/v1/user"
        },
        {
            "method":"DELETE",
            "path":"/api/acs/:id"
        },
        {
            "method":"DELETE",
            "path":"/api/v1/user/:id"
        },
        {
            "method":"PUT",
            "path":"/api/v1/user/:id"
        },
        {
            "method":"GET",
            "path":"/api/v1/user/:id"
        }
    ]
}

# 直接向数据库添加几条Policy策略
INSERT INTO `xz_boss`.`casbin_rule` (`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/user', 'POST', NULL, NULL, NULL);
INSERT INTO `xz_boss`.`casbin_rule` (`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/user/:id', 'GET', NULL, NULL, NULL);
INSERT INTO `xz_boss`.`casbin_rule` (`p_type`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`) VALUES ('p', 'admin', '/api/v1/user/:id', 'PUT', NULL, NULL, NULL);

#再测试
## 添加接口
curl -X POST -H "userName:admin" http://127.0.0.1:8080/api/v1/user
{"code":200,"message":"user add success"}
## 查询接口
curl -X GET -H "userName:admin" http://127.0.0.1:8080/api/v1/user/99
{"code":200,"message":"user Get success 99"}
## 更新接口
curl -X PUT -H "userName:admin" http://127.0.0.1:8080/api/v1/user/199
{"code":200,"message":"user update success 199"}
## 删除接口(没有分配访问权限)
curl -X DELETE -H "userName:admin" http://127.0.0.1:8080/api/v1/user/299
{"code":-1,"message":"access denied","data":null}
2.6 其他

casbin的一些适配器有自动保存功能而另外一些则没有,有自动保存功能的适配器会在连接数据的时候自动创建一张表用来保存Policy策略数据(替代存储Policy的csv文件)

上述 Demo 的SQL文件如下(该表是gorm适配器自动创建的)

casbin_rule.sql

-- ----------------------------
-- Table structure for casbin_rule
-- ----------------------------
DROP TABLE IF EXISTS `casbin_rule`;
CREATE TABLE `casbin_rule` (
  `p_type` varchar(100) DEFAULT NULL,
  `v0` varchar(100) DEFAULT NULL,
  `v1` varchar(100) DEFAULT NULL,
  `v2` varchar(100) DEFAULT NULL,
  `v3` varchar(100) DEFAULT NULL,
  `v4` varchar(100) DEFAULT NULL,
  `v5` varchar(100) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of casbin_rule
-- ----------------------------
INSERT INTO `casbin_rule` VALUES ('p', 'zhangsan', '/api/v1/ping', 'GET', null, null, null);
INSERT INTO `casbin_rule` VALUES ('p', 'coder', '/api/v2/user/:id', 'GET', null, null, null);
INSERT INTO `casbin_rule` VALUES ('p', 'coder', '/api/v2/routers', 'GET', null, null, null);
INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/api/v1/user', 'POST', null, null, null);
INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/api/v1/user/:id', 'GET', null, null, null);
INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/api/v1/user/:id', 'PUT', null, null, null);
INSERT INTO `casbin_rule` VALUES ('p', 'tom', '/api/routers', 'POST', '', '', '');

参考资料

- [1] casbin

  • 11
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值