адрес проекта:GitHub.com/значок истории/…
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. Наиболее распространенные варианты использования
- 2. Концепция
- 3. Другие примеры
- 4. Расширенные подстановочные знаки
- 5. Операционная эффективность
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
Будет выбран тот, у которого самый высокий идентификатор, а затем он будет аутентифицирован с использованием его определения разрешения.
Если одновременно существует несколько правил с наибольшим идентификатором, одно из них будет использоваться случайным образом (избегайте этого).
Вот очень простой пример:
#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"`
}
Ресурс используется для описания ресурса, к которому применяется Правило.
при исполненииIsRequestGranted(c.Request,roles)
, grbac сначала преобразует текущийRequest
со всемRule
серединаResources
совпадение.
Каждое поле ресурса поддерживаетРасширенные подстановочные знаки
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
Поля в не поддерживают «расширенные подстановочные знаки».
существуетAuthorizedRoles
иForbiddenRoles
разрешено только в*
значит все.
2.4. Loader
Загрузчик используется для загрузки правил. grbac предустановляет некоторые загрузчики, вы также можете реализоватьfunc()(grbac.Rules, error)
настроить загрузчик и пройтиgrbac.WithLoader
загрузить его.
method | description |
---|---|
WithJSON(path, interval) | регулярно изjson Конфигурация правил загрузки файлов |
WithYaml(path, interval) | регулярно изyaml Конфигурация правил загрузки файлов |
WithRules(Rules) | отgrbac.Rules Загрузить конфигурацию правила |
WithAdvancedRules(loader.AdvancedRules) | Определяйте правила более компактно и используйтеloader.AdvancedRules нагрузка |
WithLoader(loader func()(Rules, error), interval) | Периодически загружать правила с пользовательской функцией |
interval
Определяет цикл перезагрузки правил.
когдаinterval <0
час,grbac
Откажется от цикла загрузки конфигурации правил;
когдаinterval∈[0,1s)
час,grbac
будет автоматическиinterval
Установить как5s
;
3. Другие примеры
Вот несколько простых примеров, чтобы вам было легче понятьgrbac
Принцип работы.
Несмотря на то чтоgrbac
Отлично работает в большинстве http-фреймворков, но извините, сейчас я использую только gin, поэтому, пожалуйста, дайте мне знать, если в приведенном ниже примере есть какие-то недостатки.
3.1. gin && grbac.WithJSON
если хотитеJSON
файл для записи файла конфигурации, вы можете передатьgrbac.WithJSON(filepath,interval)
загрузить его,filepath
это ваш путь к файлу json, и grbac будет перезагружать файл каждый интервал. .
[
{
"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 будет перезагружать файл каждый интервал.
#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,0001 с», что является быстрым (большую часть времени при сопоставлении с подстановочными знаками).