casbin应用

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

官网:https://casbin.org/, 

https://github.com/casbin

https://casbin.org/docs/zh-CN/overview

Casbin 可以:

支持自定义请求的格式,默认的请求格式为{subject, object, action}

具有访问控制模型model和策略policy两个核心概念。

支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。

支持超级用户,如 root  Administrator,超级用户可以不受授权策略的约束访问任意资源。

支持多种内置的操作符,如 keyMatch,方便对路径式的资源进行管理,如 /foo/bar 可以映射到 /foo*

Casbin 不能:

身份认证 authentication(即验证用户的用户名、密码)casbin只负责访问控制。应该有其他专门的组件负责身份认证,然后由casbin进行访问控制,二者是相互配合的关系。

管理用户列表或角色列表 Casbin 认为由项目自身来管理用户、角色列表更为合适, 用户通常有他们的密码,但是Casbin的设计思想并不是把它作为一个存储密码的容器而是存储RBAC方案中用户和角色之间的映射关系。

基础

Casbin两个核心概念是访问控制模型model和访问控制策略policy,分别对应两个文件:model配置文件和policy策略文件。在 Casbin , 访问控制模型model被抽象为基于 PERM (Policy策略, Effect效果, Request请求, Matchers匹配器的一个文件因此切换或升级项目的授权机制与修改配置一样简单。可以通过组合可用的模型来定制访问控制模型。

Modelpolicy模板可参考:https://casbin.org/docs/zh-CN/supported-models,支持ACL,RBAC,ABAC等。

{subject, object, action}:主体subject表示用户角色,object客体表示访问路径(资源),action表示请求方法。

匹配器定义了策略是如何匹配的,可以通过直接定义实体,或者使用keyMatch方法,也可以匹配通配符。

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act  // 定义sub可以执行obj资源的act
P2 = sub, act  //定义sub所有的资源都能执行act

[role_definition]  // 基于RBAC时需要,定义role
g = _, _             //用户和角色
g2 = _, _,           //用户、角色、域

[policy_effect]
e = some(where (p.eft == allow))  // 有任意一条policy rule满足,则最终结果为allow

[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
  //定义request和policy匹配的方式,p.eft是allow还是deny基于此来决定

在安全性方面,通常会选择最简单的解决方案,因为当系统开始变复杂和难以维护时,错误就开始发生。

p, admin, /*, *
p, anonymous, /login, *
p, member, /logout, *
p, member, /member/*, *

Policy配置admin 角色可以访问所有内容,member 角色可以访问以 /member/ 开头的路径和 logout 路径,未认证用户可以登陆。

存储

官网上介绍model只能加载,不能保存。因为作者认为 model 不是动态组件,不应该在运行时进行修改,所以我们没有实现一个 API 来将 model 保存到存储中。提供了三种等效的方法来静态或动态地加载模型:

.CONF文件,代码,字符串

Policy策略存储作为adapter实现(Casbin的中间件) 实现。Casbin用户可以使用adapter从存储中加载策略规则 (aka LoadPolicy()) 或者将策略规则保存到其中 (aka SavePolicy())。 为了保持代码轻量级,没有把adapter代码放在主库中。可参考:https://github.com/HaoweiCh/go-casbin-http-authrozation mysql policy)

角色管理器用于管理Casbin中的RBAC角色层次结构(用户角色映射)。 角色管理器可以从Casbin策略规则或外部源(LDAPOktaAuth0Azure AD) 检索角色数据。 我们支持角色管理器的不同实现。 为了保持代码轻量级,我们没有把角色管理器代码放在主库中(默认的角色管理器除外) 

应用

通常casbin要与身份认证(authorization)如session管理或jwt配合使用。

gin-casbin插件插件使用参考:https://github.com/maxwellhertz/gin-casbin.git

访问资源时需要鉴权中间件判断用户合法性,并分配合适的角色。初步鉴权成功后,将用户角色,请求路径和请求方法传给casbin执行器,执行器根据modelpolicy确认该角色(subject)的用户是否允许访问该请求方法(action)和路径(object)指定的资源。若校验失败,应返回403,若通过,则继续执行后续处理函数。

参考: Go 语言中使用 casbin 实现基于角色的 HTTP 权限控制

 一般应用就是两个函数:

1. New a Casbin enforcer with a model file and a policy file:

e, _ := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
Note: you can also initialize an enforcer with policy in DB instead of file.
2. Add an enforcement hook into your code right before the access happens: sub := "alice" // the user that wants to access a resource. obj := "data1" // the resource that is going to be accessed. act := "read" // the operation that the user performs on the resource. if res := e.Enforce(sub, obj, act); res { // permit alice to read data1 } else { // deny the request, show an error } 3. Besides the static policy file, Casbin also provides API for permission management at run-time. For example, You can get all the roles assigned to a user as below: roles, _ := e.GetImplicitRolesForUser(sub)
a := mysqladapter.NewDBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/")
e := casbin.NewEnforcer("path/to/basic_model.conf", a)
_ = e.LoadPolicy()
数据库结构如下:
mysql> select * from casbin_rule;
+--------+------+---------------------------------------------------+--------+------+------+------+
| p_type | v0   | v1                                                | v2     | v3   | v4   | v5   |
+--------+------+---------------------------------------------------+--------+------+------+------+
| p      | 8881 | /base/login                                       | POST   |      |      |      |
| p      | 8881 | /base/register                                    | POST   |      |      |      |
| p      | 8881 | /api/createApi                                    | POST   |      |      |      |

示例:

package main

import (
    "errors"
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/casbin/casbin"

    "github.com/alexedwards/scs/v2"

    "testcasbin/auth"
    "testcasbin/model"
)

var sessionManager *scs.SessionManager

func main() {
    fmt.Println("hello world")
    authEnforcer, err := casbin.NewEnforcerSafe("model.conf", "policy.csv")
    if err != nil {
        log.Fatal(err)
    }

    sessionManager = scs.New()
    sessionManager.Lifetime = 2 * time.Minute

    users := createUsers()

    mux := http.NewServeMux()
    mux.HandleFunc("/login", loginHandler(users))
    mux.HandleFunc("/logout", logoutHandler())
    mux.HandleFunc("/member/id", memberHandler())
    mux.HandleFunc("/member/role", memberRoleHandler())
    mux.HandleFunc("/admin/stuff", adminHandler())

    log.Print("Server started on localhost:8088")
    log.Fatal(http.ListenAndServe(":8088", sessionManager.LoadAndSave(auth.Authorizer(authEnforcer, users, sessionManager)(mux))))
}
...

package auth

import (
    "errors"
    "log"
    "net/http"
    "testcasbin/model"

    "github.com/alexedwards/scs/v2"
    "github.com/casbin/casbin"
)

func Authorizer(e *casbin.Enforcer, users model.Users, session *scs.SessionManager) func(next http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        fn := func(w http.ResponseWriter, r *http.Request) {
            var role string
            var uid int
            role = session.GetString(r.Context(), "role")
            if role == "" {
                role = "anonymous"
            }

            if role == "member" {
                uid = session.GetInt(r.Context(), "userID")
                if uid == 0 {
                    writeError(http.StatusInternalServerError, "ERROR_NOT_GET_UID", w, errors.New("userID is not in session"))
                    return
                }
                exists := users.Exists(uid)
                if !exists {
                    writeError(http.StatusForbidden, "FORBIDDEN", w, errors.New("user does not exist"))
                    return
                }
            }

            log.Printf("role: %v, uid: %v, path: %v, method: %v\n", role, uid, r.URL.Path, r.Method)
            res, err := e.EnforceSafe(role, r.URL.Path, r.Method)
            if err != nil {
                writeError(http.StatusInternalServerError, "ERROR_CASBIN", w, err)
                return
            }
            if res {
                next.ServeHTTP(w, r)
            } else {
                writeError(http.StatusForbidden, "FORBIDDEN", w, errors.New("unauthorized"))
                return
            }
        }

        return http.HandlerFunc(fn)
    }
}

func writeError(status int, message string, w http.ResponseWriter, err error) {
    log.Print("ERROR: ", err.Error())
    w.WriteHeader(status)
    w.Write([]byte(message))
}

 

参考:

1. https://casbin.org/docs/zh-CN/overview 中文文档

2. Go 语言中使用 casbin 实现基于角色的 HTTP 权限控制  基于session身份认证

https://zupzup.org/casbin-http-role-auth/  英文

3. https://github.com/HaoweiCh/go-casbin-http-authrozation 

简单且实用的 HTTP 鉴权体系 mysql policy + redis session)

4. https://github.com/maxwellhertz/gin-casbin.git gin插件和示例(基于jwtsession

5. Casbin权限模型 csdn

6. 多租户通用权限设计(基于 casbin)

posted @ 2020-07-11 19:00  yuxi_o  阅读(1189)  评论(0编辑  收藏  举报