👮 Golang Gin/Ace/Iris/Echo RBAC 鉴权库
GRBAC
项目地址: https://github.com/storyicon/grbac
Grbac是一个快速,优雅和简洁的RBAC框架。它支持增强的通配符并使用Radix树匹配HTTP请求。令人惊奇的是,您可以在任何现有的数据库和数据结构中轻松使用它。
grbac的作用是确保指定的资源只能由指定的角色访问。请注意,grbac不负责存储鉴权规则和分辨“当前请求发起者具有哪些角色”,更不负责角色的创建、分配等。这意味着您应该首先配置规则信息,并提供每个请求的发起者具有的角色。
grbac将Host
、Path
和Method
的组合视为Resource
,并将Resource
绑定到一组角色规则(称为Permission
)。只有符合这些规则的用户才能访问相应的Resource
。
读取鉴权规则的组件称为Loader
。grbac预置了一些Loader
,你也可以通过实现func()(grbac.Rules,error)
来根据你的设计来自定义Loader
,并通过grbac.WithLoader
加载它。
1. 最常见的用例
下面是最常见的用例,它使用gin
,并将grbac
包装成了一个中间件。通过这个例子,你可以很容易地知道如何在其他http框架中使用grbac
(比如echo
,iris
,ace
等):
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
由三部分组成:ID
,Resource
和Permission
。
“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
中的字段不支持“增强的通配符”。
在AuthorizedRoles
和ForbiddenRoles
中只允许*
表示所有。
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:password@/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”,这很快(大多数时间在通配符的匹配上)。
👮 Golang Gin/Ace/Iris/Echo RBAC 鉴权库的更多相关文章
- 【AMAD】django-rules -- 强大的Django鉴权库,不需要数据库
动机 简介 个人评分 动机 Django默认的权限原子级别是model级.但是一些时候我们像针对model每条数据库记录都进行权限空,也就是对象级权限控制. 简介 django-rules是一个Dja ...
- Kubernetes K8S之鉴权RBAC详解
Kubernetes K8S之鉴权概述与RBAC详解 K8S认证与授权 认证「Authentication」 认证有如下几种方式: 1.HTTP Token认证:通过一个Token来识别合法用户. H ...
- 【Gin-API系列】Gin中间件之鉴权访问(五)
在完成中间件的介绍和日志中间件的代码后,我们的程序已经基本能正常跑通了,但如果要上生产,还少了一些必要的功能,例如鉴权.异常捕捉等.本章我们介绍如何编写鉴权中间件. 鉴权访问,说白了就是给用户的请求增 ...
- 深入理解k8s中的访问控制(认证、鉴权、审计)流程
Kubernetes自身并没有用户管理能力,无法像操作Pod一样,通过API的方式创建/删除一个用户实例,也无法在etcd中找到用户对应的存储对象. 在Kubernetes的访问控制流程中,用户模型是 ...
- jmeter测试wordpress使用base64进行鉴权
1.下载插件 对密码进行加密 2.安装插件 3.使用插件 4.使用base64对上面获取到的密码进行二次加密 echo -n "用户:密码" | base64 将以上命令键入lin ...
- WebAPI常见的鉴权方法,及其适用范围
在谈这个问题之前,我们先来说说在WebAPI中保障接口请求合法性的常见办法: API Key + API Secret cookie-session认证 OAuth JWT 当然还有很多其它的,比如 ...
- 七牛云 PHP SDK服务器鉴权失败!参数解释
昨天搞了一下午,用7牛官方的SDK demo 1.上传凭证 $policy = array( 'callbackUrl' => 'http://api.example.com/qiniu/upl ...
- HTTP基本认证和JWT鉴权
一.HTTP基本认证 Basic Authentication——当浏览器访问使用基本认证的网站的时候, 浏览器会提示你输入用户名和密码. http auth的过程: · 客户端发送http请求 · ...
- Golang Gin实践 番外 请入门 Makefile
Golang Gin实践 番外 请入门 Makefile 原文地址:Golang Gin实践 番外 请入门 Makefile 前言 含一定复杂度的软件工程,基本上都是先编译 A,再依赖 B,再编译 C ...
随机推荐
- 追本溯源 —— 句型、表达、模式,pattern,著名的话
** 时候,做了 ** 事,是我 **,做得最对的一件事: "Winning that ticket was the best thing that ever happened to me& ...
- WPF 获取 ListView DataTemplate 中控件值
原文:WPF 获取 ListView DataTemplate 中控件值 版权声明:本文为博主原创文章,未经博主允许可以随意转载 https://blog.csdn.net/songqingwei19 ...
- 《Linux Device Drivers》第十一章 核心数据类型——note
基本介绍 因为Linux多平台特性,不管是哪一个重要驱动力应该是便携 与内核代码相关的核心问题应该是访问的同时是数据项的已知长度.能力和利用不同的处理器 内核使用的数据类型主要分为三类 类似int这种 ...
- Matlab随笔之画图函数总结
原文:Matlab随笔之画图函数总结 MATLAB函数画图 MATLAB不但擅长於矩阵相关的数值运算,也适合用在各种科学目视表示(Scientific visualization).本节将介绍MATL ...
- 避免让WPF资源字典变得杂乱臃肿
原文:避免让WPF资源字典变得杂乱臃肿 避免让WPF资源字典变得杂乱臃肿 周银辉 今天看到项目种的一个XXXResource.xaml文件代码 ...
- Java 阅读TXT文件
public class GenCategoryAttrItemHandler { private final static String INPUT_FILE_PATH = "input/ ...
- strlen, wcslen, _mbslen, _mbslen_l, _mbstrlen, _mbstrlen_l, setlocale(LC_CTYPE, "Japanese_Japan")(MSDN的官方示例)
// crt_strlen.c // Determine the length of a string. For the multi-byte character // example to work ...
- MVC 自动装配
//HelloController.cs using FirstMVC.Models; using System;using System.Collections.Generic;using Syst ...
- 【wpf】在win10系统上弹出toast和notification
原文:[wpf]在win10系统上弹出toast和notification 老规矩,先看效果 右下角的notification: 操作中心的notification: 整体效果: 前提条件 1.需要在 ...
- js如何通过变量调用函数,函数名在变量里面
js如何通过变量调用函数,函数名在变量里面. 有时候函数名是动态定义的,这时候我们就需要用到这个方法了. //赋值函数名称 var a = "b"; //定义函数 function ...