Golang Gin/Ace/Iris/Echo RBAC 鉴权库

GRBAC

项目地址: https://github.com/storyicon/grbac

Grbac是一个快速,优雅和简洁的RBAC框架。它支持增强的通配符并使用Radix树匹配HTTP请求。令人惊奇的是,您可以在任何现有的数据库和数据结构中轻松使用它。

grbac的作用是确保指定的资源只能由指定的角色访问。请注意,grbac不负责存储鉴权规则和分辨“当前请求发起者具有哪些角色”,更不负责角色的创建、分配等。这意味着您应该首先配置规则信息,并提供每个请求的发起者具有的角色。

grbac将HostPathMethod的组合视为Resource,并将Resource绑定到一组角色规则(称为Permission)。只有符合这些规则的用户才能访问相应的Resource

读取鉴权规则的组件称为Loader。grbac预置了一些Loader,你也可以通过实现func()(grbac.Rules,error)来根据你的设计来自定义Loader,并通过grbac.WithLoader加载它。

  • 1. 最常见的用例
  • 2. 概念
    • 2.1. Rule
    • 2.2. Resource
    • 2.3. Permission
    • 2.4. Loader
  • 3. 其他例子
    • 3.1. gin && grbac.WithJSON
    • 3.2. echo && grbac.WithYaml
    • 3.3. iris && grbac.WithRules
    • 3.4. ace && grbac.WithAdvancedRules
    • 3.5. gin && grbac.WithLoader
  • 4. 增强的通配符
  • 5. 运行效率

1. 最常见的用例

下面是最常见的用例,它使用gin,并将grbac包装成了一个中间件。通过这个例子,你可以很容易地知道如何在其他http框架中使用grbac(比如echoirisace等):

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/storyicon/grbac"
    "net/http"
    "time"
)

func LoadAuthorizationRules() (rules grbac.Rules, err error) {
    // 在这里实现你的逻辑
    // ...
    // 你可以从数据库或文件加载授权规则
    // 但是你需要以 grbac.Rules 的格式返回你的身份验证规则
    // 提示:你还可以将此函数绑定到golang结构体
    return
}

func QueryRolesByHeaders(header http.Header) (roles []string,err error){
    // 在这里实现你的逻辑
    // ...
    // 这个逻辑可能是从请求的Headers中获取token,并且根据token从数据库中查询用户的相应角色。
    return roles, err
}

func Authorization() gin.HandlerFunc {
    // 在这里,我们通过“grbac.WithLoader”接口使用自定义Loader功能
    // 并指定应每分钟调用一次LoadAuthorizationRules函数以获取最新的身份验证规则。
    // Grbac还提供一些现成的Loader:
    // grbac.WithYAML
    // grbac.WithRules
    // grbac.WithJSON
    // ...
    rbac, err := grbac.New(grbac.WithLoader(LoadAuthorizationRules, time.Minute))
    if err != nil {
        panic(err)
    }
    return func(c *gin.Context) {
        roles, err := QueryRolesByHeaders(c.Request.Header)
        if err != nil {
            c.AbortWithError(http.StatusInternalServerError, err)
            return
        }
        state, _ := rbac.IsRequestGranted(c.Request, roles)
        if !state.IsGranted() {
            c.AbortWithStatus(http.StatusUnauthorized)
            return
        }
    }
}

func main(){
    c := gin.New()
    c.Use(Authorization())

    // 在这里通过c.Get、c.Post等函数绑定你的API
    // ...

    c.Run(":8080")
}

2. 概念

这里有一些关于grbac的概念。这很简单,你可能只需要三分钟就能理解。

2.1. Rule

// Rule即规则,用于定义Resource和Permission之间的关系
type Rule struct {
    // ID决定了Rule的优先级。
    // ID值越大意味着Rule的优先级越高。
    // 当请求被多个规则同时匹配时,grbac将仅使用具有最高ID值的规则。
    // 如果有多个规则同时具有最大的ID,则将随机使用其中一个规则。
    ID int `json:"id"`
    *Resource
    *Permission
}

如你所见,Rule由三部分组成:IDResourcePermission
“ID”确定规则的优先级。
当请求同时满足多个规则时(例如在通配符中),
grbac将选择具有最高ID的那个,然后使用其权限定义进行身份验证。
如果有多个规则同时具有最大的ID,则将随机使用其中一个规则(所以请避免这种情况)。

下面有一个非常简单的例子:

#Rule
- id: 0
  # Resource
  host: "*"
  path: "**"
  method: "*"
  # Permission
  authorized_roles:
  - "*"
  forbidden_roles: []
  allow_anyone: false

#Rule
- id: 1
  # Resource
  host: domain.com
  path: "/article"
  method: "{DELETE,POST,PUT}"
  # Permission
  authorized_roles:
  - editor
  forbidden_roles: []
  allow_anyone: false

在以yaml格式编写的此配置文件中,ID=0 的规则表明任何具有任何角色的人都可以访问所有资源。
但是ID=1的规则表明只有editor可以对文章进行增删改操作。
这样,除了文章的操作只能由editor访问之外,任何具有任何角色的人都可以访问所有其他资源。

2.2. Resource

type Resource struct {
    // Host 定义资源的Host,允许使用增强的通配符。
    Host string `json:"host"`
    // Path 定义资源的Path,允许使用增强的通配符。
    Path string `json:"path"`
    // Method 定义资源的Method,允许使用增强的通配符。
    Method string `json:"method"`
}

Resource用于描述Rule适用的资源。
当执行IsRequestGranted(c.Request,roles)时,grbac首先将当前的Request与所有Rule中的Resources匹配。

Resource的每个字段都支持增强的通配符

2.3. Permission

// Permission用于定义权限控制信息
type Permission struct {
    // AuthorizedRoles定义允许访问资源的角色
    // 支持的类型: 非空字符串,*
    //      *: 意味着任何角色,但访问者应该至少有一个角色,
    //      非空字符串:指定的角色
    AuthorizedRoles []string `json:"authorized_roles"`
    // ForbiddenRoles 定义不允许访问指定资源的角色
    // ForbiddenRoles 优先级高于AuthorizedRoles
    // 支持的类型:非空字符串,*
    //      *: 意味着任何角色,但访问者应该至少有一个角色,
    //      非空字符串:指定的角色
    //
    ForbiddenRoles []string `json:"forbidden_roles"`
    // AllowAnyone的优先级高于 ForbiddenRoles、AuthorizedRoles
    // 如果设置为true,任何人都可以通过验证。
    // 请注意,这将包括“没有角色的人”。
    AllowAnyone bool `json:"allow_anyone"`
}

“Permission”用于定义绑定到的“Resource”的授权规则。
这是易于理解的,当请求者的角色符合“Permission”的定义时,他将被允许访问Resource,否则他将被拒绝访问。

为了加快验证的速度,Permission中的字段不支持“增强的通配符”。
AuthorizedRolesForbiddenRoles中只允许*表示所有。

2.4. Loader

Loader用于加载Rule。 grbac预置了一些加载器,你也可以通过实现func()(grbac.Rules, error) 来自定义加载器并通过 grbac.WithLoader 加载它。

method description
WithJSON(path, interval) 定期从json文件加载规则配置
WithYaml(path, interval) 定期从yaml文件加载规则配置
WithRules(Rules) grbac.Rules加载规则配置
WithAdvancedRules(loader.AdvancedRules) 以一种更紧凑的方式定义Rule,并使用loader.AdvancedRules加载
WithLoader(loader func()(Rules, error), interval) 使用自定义函数定期加载规则

interval定义了Rules的重载周期。
interval <0时,grbac会放弃周期加载Rules配置;
interval∈[0,1s)时,grbac会自动将interval设置为5s;

3. 其他例子

这里有一些简单的例子,可以让你更容易理解grbac的工作原理。
虽然grbac在大多数http框架中运行良好,但很抱歉我现在只使用gin,所以如果下面的例子中有一些缺陷,请告诉我。

3.1. gin && grbac.WithJSON

如果你想在JSON文件中编写配置文件,你可以通过grbac.WithJSON(filepath,interval)加载它,filepath是你的json文件路径,并且grbac将每隔interval重新加载一次文件。 。

[
    {
        "id": 0,
        "host": "*",
        "path": "**",
        "method": "*",
        "authorized_roles": [
            "*"
        ],
        "forbidden_roles": [
            "black_user"
        ],
        "allow_anyone": false
    },
    {
        "id":1,
        "host": "domain.com",
        "path": "/article",
        "method": "{DELETE,POST,PUT}",
        "authorized_roles": ["editor"],
        "forbidden_roles": [],
        "allow_anyone": false
    }
]

以上是“JSON”格式的身份验证规则示例。它的结构基于grbac.Rules。


func QueryRolesByHeaders(header http.Header) (roles []string,err error){
    // 在这里实现你的逻辑
    // ...
    // 这个逻辑可能是从请求的Headers中获取token,并且根据token从数据库中查询用户的相应角色。
    return roles, err
}

func Authentication() gin.HandlerFunc {
    rbac, err := grbac.New(grbac.WithJSON("config.json", time.Minute * 10))
    if err != nil {
        panic(err)
    }
    return func(c *gin.Context) {
        roles, err := QueryRolesByHeaders(c.Request.Header)
        if err != nil {
            c.AbortWithError(http.StatusInternalServerError, err)
            return
        }

        state, err := rbac.IsRequestGranted(c.Request, roles)
        if err != nil {
            c.AbortWithStatus(http.StatusInternalServerError)
            return
        }

        if !state.IsGranted() {
            c.AbortWithStatus(http.StatusInternalServerError)
            return
        }
    }
}

func main(){
    c := gin.New()
    c.Use(Authentication())

    // 在这里通过c.Get、c.Post等函数绑定你的API
    // ...

    c.Run(":8080")
}

3.2. echo && grbac.WithYaml

如果你想在YAML文件中编写配置文件,你可以通过grbac.WithYAML(file,interval)加载它,file是你的yaml文件路径,并且grbac将每隔一个interval重新加载一次文件。

#Rule
- id: 0
  # Resource
  host: "*"
  path: "**"
  method: "*"
  # Permission
  authorized_roles:
  - "*"
  forbidden_roles: []
  allow_anyone: false

#Rule
- id: 1
  # Resource
  host: domain.com
  path: "/article"
  method: "{DELETE,POST,PUT}"
  # Permission
  authorized_roles:
  - editor
  forbidden_roles: []
  allow_anyone: false

以上是“YAML”格式的认证规则的示例。它的结构基于grbac.Rules。

func QueryRolesByHeaders(header http.Header) (roles []string,err error){
    // 在这里实现你的逻辑
    // ...
    // 这个逻辑可能是从请求的Headers中获取token,并且根据token从数据库中查询用户的相应角色。
    return roles, err
}

func Authentication() echo.MiddlewareFunc {
    rbac, err := grbac.New(grbac.WithYAML("config.yaml", time.Minute * 10))
    if err != nil {
            panic(err)
    }
    return func(echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            roles, err := QueryRolesByHeaders(c.Request().Header)
            if err != nil {
                    c.NoContent(http.StatusInternalServerError)
                    return nil
            }
            state, err := rbac.IsRequestGranted(c.Request(), roles)
            if err != nil {
                    c.NoContent(http.StatusInternalServerError)
                    return nil
            }
            if state.IsGranted() {
                    return nil
            }
            c.NoContent(http.StatusUnauthorized)
            return nil
        }
    }
}

func main(){
    c := echo.New()
    c.Use(Authentication())

    // 在这里通过c.Get、c.Post等函数绑定你的API
    // ...

}

3.3. iris && grbac.WithRules

如果你想直接在代码中编写认证规则,grbac.WithRules(rules)提供了这种方式,你可以像这样使用它:


func QueryRolesByHeaders(header http.Header) (roles []string,err error){
    // 在这里实现你的逻辑
    // ...
    // 这个逻辑可能是从请求的Headers中获取token,并且根据token从数据库中查询用户的相应角色。
    return roles, err
}

func Authentication() iris.Handler {
    var rules = grbac.Rules{
        {
            ID: 0,
            Resource: &grbac.Resource{
                        Host: "*",
                Path: "**",
                Method: "*",
            },
            Permission: &grbac.Permission{
                AuthorizedRoles: []string{"*"},
                ForbiddenRoles: []string{"black_user"},
                AllowAnyone: false,
            },
        },
        {
            ID: 1,
            Resource: &grbac.Resource{
                    Host: "domain.com",
                Path: "/article",
                Method: "{DELETE,POST,PUT}",
            },
            Permission: &grbac.Permission{
                    AuthorizedRoles: []string{"editor"},
                ForbiddenRoles: []string{},
                AllowAnyone: false,
            },
        },
    }
    rbac, err := grbac.New(grbac.WithRules(rules))
    if err != nil {
        panic(err)
    }
    return func(c context.Context) {
        roles, err := QueryRolesByHeaders(c.Request().Header)
        if err != nil {
                c.StatusCode(http.StatusInternalServerError)
            c.StopExecution()
            return
        }
        state, err := rbac.IsRequestGranted(c.Request(), roles)
        if err != nil {
                c.StatusCode(http.StatusInternalServerError)
            c.StopExecution()
            return
        }
        if !state.IsGranted() {
                c.StatusCode(http.StatusUnauthorized)
            c.StopExecution()
            return
        }
    }
}

func main(){
    c := iris.New()
    c.Use(Authentication())

    // 在这里通过c.Get、c.Post等函数绑定你的API
    // ...

}

3.4. ace && grbac.WithAdvancedRules

如果你想直接在代码中编写认证规则,grbac.WithAdvancedRules(rules)提供了这种方式,你可以像这样使用它:


func QueryRolesByHeaders(header http.Header) (roles []string,err error){
    // 在这里实现你的逻辑
    // ...
    // 这个逻辑可能是从请求的Headers中获取token,并且根据token从数据库中查询用户的相应角色。
    return roles, err
}

func Authentication() ace.HandlerFunc {
    var advancedRules = loader.AdvancedRules{
        {
            Host: []string{"*"},
            Path: []string{"**"},
            Method: []string{"*"},
            Permission: &grbac.Permission{
                AuthorizedRoles: []string{},
                ForbiddenRoles: []string{"black_user"},
                AllowAnyone: false,
            },
        },
        {
            Host: []string{"domain.com"},
            Path: []string{"/article"},
            Method: []string{"PUT","DELETE","POST"},
            Permission: &grbac.Permission{
                AuthorizedRoles: []string{"editor"},
                ForbiddenRoles: []string{},
                AllowAnyone: false,
            },
        },
    }
    auth, err := grbac.New(grbac.WithAdvancedRules(advancedRules))
    if err != nil {
        panic(err)
    }
    return func(c *ace.C) {
        roles, err := QueryRolesByHeaders(c.Request.Header)
        if err != nil {
        c.AbortWithStatus(http.StatusInternalServerError)
            return
        }
        state, err := auth.IsRequestGranted(c.Request, roles)
        if err != nil {
            c.AbortWithStatus(http.StatusInternalServerError)
            return
        }
        if !state.IsGranted() {
            c.AbortWithStatus(http.StatusUnauthorized)
            return
        }
    }
}

func main(){
    c := ace.New()
    c.Use(Authentication())

    // 在这里通过c.Get、c.Post等函数绑定你的API
    // ...

}

loader.AdvancedRules试图提供一种比grbac.Rules更紧凑的定义鉴权规则的方法。

3.5. gin && grbac.WithLoader


func QueryRolesByHeaders(header http.Header) (roles []string,err error){
    // 在这里实现你的逻辑
    // ...
    // 这个逻辑可能是从请求的Headers中获取token,并且根据token从数据库中查询用户的相应角色。
    return roles, err
}

type MySQLLoader struct {
    session *gorm.DB
}

func NewMySQLLoader(dsn string) (*MySQLLoader, error) {
    loader := &MySQLLoader{}
    db, err := gorm.Open("mysql", dsn)
    if err  != nil {
        return nil, err
    }
    loader.session = db
    return loader, nil
}

func (loader *MySQLLoader) LoadRules() (rules grbac.Rules, err error) {
    // 在这里实现你的逻辑
    // ...
    // 你可以从数据库或文件加载授权规则
    // 但是你需要以 grbac.Rules 的格式返回你的身份验证规则
    // 提示:你还可以将此函数绑定到golang结构体
    return
}

func Authentication() gin.HandlerFunc {
    loader, err := NewMySQLLoader("user:[email protected]/dbname?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
        panic(err)
    }
    rbac, err := grbac.New(grbac.WithLoader(loader.LoadRules, time.Second * 5))
    if err != nil {
        panic(err)
    }
    return func(c *gin.Context) {
        roles, err := QueryRolesByHeaders(c.Request.Header)
        if err != nil {
            c.AbortWithStatus(http.StatusInternalServerError)
            return
        }

        state, err := rbac.IsRequestGranted(c.Request, roles)
        if err != nil {
            c.AbortWithStatus(http.StatusInternalServerError)
            return
        }
        if !state.IsGranted() {
            c.AbortWithStatus(http.StatusUnauthorized)
            return
        }
    }
}

func main(){
    c := gin.New()
    c.Use(Authorization())

    // 在这里通过c.Get、c.Post等函数绑定你的API
    // ...

    c.Run(":8080")
}

4. 增强的通配符

Wildcard支持的语法:

pattern:
  { term }
term:
  ‘*‘         匹配任何非路径分隔符的字符串
  ‘**‘        匹配任何字符串,包括路径分隔符.
  ‘?‘         匹配任何单个非路径分隔符
  ‘[‘ [ ‘^‘ ] { character-range } ‘]‘
        character class (must be non-empty)
  ‘{‘ { term } [ ‘,‘ { term } ... ] ‘}‘
  c           匹配字符 c (c != ‘*‘, ‘?‘, ‘\\‘, ‘[‘)
  ‘\\‘ c      匹配字符 c

character-range:
  c           匹配字符 c (c != ‘\\‘, ‘-‘, ‘]‘)
  ‘\\‘ c      匹配字符 c
  lo ‘-‘ hi   匹配字符 c for lo <= c <= hi

5. 运行效率

? gos test -bench=.
goos: linux
goarch: amd64
pkg: github.com/storyicon/grbac/pkg/tree
BenchmarkTree_Query                   2000           541397 ns/op
BenchmarkTree_Foreach_Query           2000           1360719 ns/op
PASS
ok      github.com/storyicon/grbac/pkg/tree     13.182s

测试用例包含1000个随机规则,“BenchmarkTree_Query”和“BenchmarkTree_Foreach_Query”函数分别测试四个请求:

541397/(4*1e9)=0.0001s

当有1000条规则时,每个请求的平均验证时间为“0.0001s”,这很快(大多数时间在通配符的匹配上)。

原文地址:https://blog.51cto.com/14053986/2417142

时间: 2024-10-02 23:56:03

Golang Gin/Ace/Iris/Echo RBAC 鉴权库的相关文章

web系统中ACL, RBAC等鉴权系统的异同

ACL, 强调面向资源, 描述对具体资源对象的操作鉴权, 有诸如Zend_ACL(好用), symfony-acl(不好用)等实现 应用场景如:对一条帖子资源的增删改鉴权, 整个鉴权流程中, 权限部分是由具体帖子对象和增删改等操作共同构成的.授权的结果是将 "资源---操作" 组合授权给用户. RBAC,强调面向角色的权限节点鉴权, 描述对功能节点的鉴权,有诸如ThinkPHP-RBAC, sylius/rbac-bundle(symfony)等实现 应用场景如: 依据不同用户角色显示

CDN a,b,c三种鉴权的PHP代码

A鉴权方式的代码 //http://DomainName/Filename?auth_key=timestamp-rand-uid-md5hash //sstring = "URI-Timestamp-rand-uid-PrivateKey" (URI是用户的请求对象相对地址, 如 /Filename) //HashValue = md5sum(sstring) function PrivateKeyA(){ $time=strtotime("+8 hours");

WebAPI常见的鉴权方法,及其适用范围

在谈这个问题之前,我们先来说说在WebAPI中保障接口请求合法性的常见办法: API Key + API Secret cookie-session认证 OAuth JWT 当然还有很多其它的,比如 openid connect (OAuth 2.0协议之上的简单身份层),Basic Auth ,Digest Auth 不一一例举了 1.API Key + API Secret Resource + API Key + API Secret 匹配正确后,才可以访问Resource,通常还会配合时

HTTP基本认证和JWT鉴权

一.HTTP基本认证 Basic Authentication--当浏览器访问使用基本认证的网站的时候, 浏览器会提示你输入用户名和密码. http auth的过程: · 客户端发送http请求 · 服务器发现配置了http auth,于是检查request里面有没有"Authorization"的http header · 如果有,则判断Authorization里面的内容是否在用户列表里面,Authorization header的典型数据为"Authorization:

带鉴权信息的SIP呼叫

INVITE sip:[email protected]/2.0 Via: SIP/2.0/UDP192.168.50.32:2445;branch=z9hG4bK-d8754z-244fd550d2729557-1---d8754z-;rport Max-Forwards: 70 Contact:<sip:[email protected]:2445> To: <sip:[email protected]> From:"1002"<sip:[email 

WebSocket 的鉴权授权方案

引子 WebSocket 是个好东西,为我们提供了便捷且实时的通讯能力.然而,对于 WebSocket 客户端的鉴权,协议的 RFC 是这么说的: This protocol doesn't prescribe any particular way that servers canauthenticate clients during the WebSocket handshake. The WebSocketserver can use any client authentication me

无线端安全登录与鉴权一之Kerberos

无线端登录与鉴权是安全登录以及保证用户数据安全的第一步,也是最重要的一步.之前做过一个安全登录与鉴权的方案,借这个机会,系统的思考一下,与大家交流交流 先介绍一下TX系统使用的Kerberos方案,参考了 http://blog.csdn.net/wulantian/article/details/42418231 的文章 一.概念介绍 Kerberos:起源于希腊神话,是一支守护着冥界长着3个头颅的神犬,在keberos Authentication中,Kerberos的3个头颅代表中认证过程

开放平台鉴权以及OAuth2.0介绍

OAuth 2.0 协议 OAuth是一个开发标准,允许用户授权第三方网站或应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的内容. OAuth 2.0不兼容1.0. 协议的参与者 RO (resource owner): 资源所有者,对资源具有授权能力的人. RS (resource server): 资源服务器,它存储资源,并处理对资源的访问请求. Client: 第三方应用,它获得RO的授权后便可以去访问RO的资源. AS (authoriz

api-gateway实践(7)鉴权场景和网关场景梳理、OAuth2待澄清问题列表

一.身份鉴权验证 1.业务请求 1.1.父元素声明了 "/GroupA/VersionB/*",子元素声明了 "/GroupA/VersionB/Cxxx",access="ROLE_XXXX" 身份识别: 有效token 无token.无效token   权限鉴别: 有权限: 无权限: 1.2.父元素声明了 "/GroupA/VersionB/*",子元素没有声明的 "/GroupA/VersionB/Dxxx&q