美文网首页Go语言自习室
Go语言 gin框架集成Casbin访问权限控制

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

作者: 楚江云 | 来源:发表于2020-10-09 17:38 被阅读0次

    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 文件
    ACL basic_model.conf basic_policy.csv
    具有超级用户的ACL basic_with_root_model.conf basic_policy.csv
    没有用户的ACL basic_without_users_model.conf basic_without_users_policy.csv
    没有资源的ACL basic_without_resources_model.conf basic_without_resources_policy.csv
    RBAC rbac_model.conf rbac_policy.csv
    支持资源角色的RBAC rbac_with_resource_roles_model.conf rbac_with_resource_roles_policy.csv
    支持域/租户的RBAC rbac_with_domains_model.conf rbac_with_domains_policy.csv
    ABAC abac_model.conf
    RESTful keymatch_model.conf keymatch_policy.csv
    拒绝优先 rbac_with_not_deny_model.conf rbac_with_deny_policy.csv
    Allow-and-deny rbac_with_deny_model.conf rbac_with_deny_policy.csv
    Priority priority_model.conf priority_policy.csv
    1.2.2 适配器存储

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

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

    适配器 类型 作者 自动保存 描述
    File Adapter (内置) File Casbin For .CSV (Comma-Separated Values) files
    Filtered File Adapter (内置) File @faceless-saint For .CSV (Comma-Separated Values) files with policy subset loading support
    SQL Adapter SQL @Blank-Xu MySQL, PostgreSQL, SQL Server, SQLite3 are supported in master branch and Oracle is supported in oracle branch by database/sql
    Xorm Adapter ORM Casbin MySQL, PostgreSQL, TiDB, SQLite, SQL Server, Oracle are supported by Xorm
    Gorm Adapter ORM Casbin MySQL, PostgreSQL, Sqlite3, SQL Server are supported by Gorm
    Beego ORM Adapter ORM Casbin MySQL, PostgreSQL, Sqlite3 are supported by Beego ORM
    SQLX Adapter ORM @memwey MySQL, PostgreSQL, SQLite, Oracle are supported by SQLX
    Sqlx Adapter SQL @Blank-Xu MySQL, PostgreSQL, SQL Server, SQLite3 are supported in master branch and Oracle is supported in oracle branch by sqlx
    GF ORM Adapter ORM @vance-liu MySQL, SQLite, PostgreSQL, Oracle, SQL Server are supported by GF ORM
    Filtered PostgreSQL Adapter SQL Casbin For PostgreSQL
    PostgreSQL Adapter SQL @cychiuae For PostgreSQL
    PostgreSQL Adapter (Archived) SQL Going For PostgreSQL
    RQLite Adapter SQL EDOMO Systems For RQLite
    MongoDB Adapter NoSQL Casbin For MongoDB based on MongoDB driver for Go
    MongoDB Adapter NoSQL Titan DC For MongoDB based on MongoDB Go driver
    RethinkDB Adapter NoSQL @adityapandey9 For RethinkDB
    Cassandra Adapter NoSQL Casbin For Apache Cassandra DB
    DynamoDB Adapter NoSQL HOOQ For Amazon DynamoDB
    Dynacasbin NoSQL NewbMiao For Amazon DynamoDB
    ArangoDB Adapter NoSQL @adamwasila For ArangoDB
    Amazon S3 Adapter Cloud Soluto For Minio and Amazon S3
    Azure Cosmos DB Adapter Cloud @spacycoder For Microsoft Azure Cosmos DB
    GCP Datastore Adapter Cloud LivingPackets For Google Cloud Platform Datastore
    GCP Firestore Adapter Cloud @reedom For Google Cloud Platform Firestore
    Consul Adapter KV store @ankitm123 For HashiCorp Consul
    Redis Adapter KV store Casbin For Redis
    Etcd Adapter KV store @sebastianliu For etcd
    BoltDB Adapter KV store @speza For Bolt
    Bolt Adapter KV store @wirepair For Bolt
    BadgerDB Adapter KV store @inits For BadgerDB
    Protobuf Adapter Stream Casbin For Google Protocol Buffers
    JSON Adapter String Casbin For JSON
    String Adapter String @qiangmzsx For 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

    相关文章

      网友评论

        本文标题:Go语言 gin框架集成Casbin访问权限控制

        本文链接:https://www.haomeiwen.com/subject/nhzlpktx.html