casbin of Go ежедневная библиотека

Go

Введение

Управление привилегиями является обязательным модулем почти в каждой системе. Если при разработке проекта необходимо один раз реализовать управление разрешениями, это, несомненно, приведет к пустой трате времени и увеличению затрат на разработку. следовательно,casbinПоявилась библиотека.casbinЭто мощная и эффективная библиотека управления доступом. Поддерживает множество широко используемых моделей контроля доступа, таких какACL/RBAC/ABACЖдать.可以实现灵活的访问权限控制。 в то же время,casbinПоддерживает несколько языков программирования,Go/Java/Node/PHP/Python/.NET/Rust.我们只需要Узнай один раз, используй много.

быстрый в использовании

Мы по-прежнему используем Go Module для написания кода, сначала инициализируем:

$ mkdir casbin && cd casbin
$ go mod init github.com/darjun/go-daily-lib/casbin

Затем установитеcasbin,В настоящее времяv2Версия:

$ go get github.com/casbin/casbin/v2

Разрешение действительно контролируетВОЗдакакой ресурсчто делать.casbinАбстрактная модель для контроля доступа на основе Action Perm (политика, эффект, эффект, запрос, сопоставление) профиль метамодели (файл модели). Таким образом, коммутатор или обновление механизмов авторизации просто нужно изменить файл конфигурации.

policyЭто определение политики или правил. Он определяет конкретные правила.

requestэто абстракция для запросов доступа, которая связана сe.Enforce()Параметры функции имеют взаимно однозначное соответствие

matcherСопоставитель сопоставит запрос с каждым из определенныхpolicyСопоставьте один за другим, чтобы получить несколько совпадающих результатов.

effectНа основе агрегирования всех результатов применения сопоставителя к запросу принимается решение о том, является ли запроспозволятьвсе ещеМусор.

Следующая диаграмма хорошо изображает этот процесс:

Начнем с написания файла модели:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

[policy_effect]
e = some(where (p.eft == allow))

В приведенном выше файле модели указано, что разрешения предоставляютсяsub,obj,actОн состоит из трех элементов, и запрос может быть передан только в том случае, если в списке политик есть точно такая же политика, как и она. Результат сопоставления можно получить с помощьюp.eftПолучать,some(where (p.eft == allow))Указывает, что только одна политика разрешает это.

Затем наш файл политики (т.е. кто что может делать с каким ресурсом):

p, dajun, data1, read
p, lizi, data2, write

вышеpolicy.csvДве строки файла представляютdajunк даннымdata1имеютreadОрган власти,liziк даннымdata2имеютwriteразрешения.

Далее используется код:

package main

import (
  "fmt"
  "log"

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

func check(e *casbin.Enforcer, sub, obj, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s\n", sub, act, obj)
  } else {
    fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}

Код на самом деле не сложный. Сначала создайтеcasbin.Enforcerобъект, загрузить файл моделиmodel.confи файлы политикиpolicy.csv, позвони егоEnforceМетод проверки разрешений. Запустите программу:

$ go run main.go
dajun CAN read data1
lizi CAN write data2
dajun CANNOT write data1
dajun CANNOT read data2

Запрос должен точно соответствовать политике для прохождения.("dajun", "data1", "read")совпадениеp, dajun, data1, read,("lizi", "data2", "write")совпадениеp, lizi, data2, write, поэтому первые две проверки проходят. 3-й, потому что"dajun"без правdata1изwriteразрешения, 4-й, потому чтоdajunправильноdata2нетreadразрешения, поэтому проверка не проходит. Результат соответствует ожиданиям.

sub/obj/actсоответственноEnforceТри параметра метода. На самом деле здесьsub/obj/actиread/write/data1/data2Я выбрал его наугад, вы можете использовать любое другое имя, если оно соответствует.

То, что достигается в приведенном выше примере,ACL(список контроля доступа, список контроля доступа).ACLРазрешения каждого субъекта на каждый ресурс определены, а те, которые не определены, не имеют разрешений. Мы также можем добавить суперадминистраторов, суперадминистраторы могут делать все что угодно. Предположим, что суперадминистраторroot, нам просто нужно изменить сопоставитель:

[matchers]
e = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"

до тех пор, пока принцип доступаrootВсегда отпускай.

проверять:

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "root", "data1", "read")
  check(e, "root", "data2", "write")
  check(e, "root", "data1", "execute")
  check(e, "root", "data3", "rwx")
}

так какsub = "root", сопоставитель должен пройти, и результат:

$ go run main.go
root CAN read data1
root CAN write data2
root CAN execute data1
root CAN rwx data3

модель RBAC

ACLНет проблем с моделью, когда пользователей и ресурсов мало, а количество пользователей и ресурсов велико.ACLстановится чрезвычайно громоздким. Представьте, как больно сбрасывать нужные ему разрешения каждый раз, когда добавляется новый пользователь.RBAC(ролевой контроль доступа) путем введения ролей (role) этот средний слой, чтобы решить эту проблему. Каждый пользователь принадлежит определенной роли, такой как разработчик, администратор, эксплуатация и техническое обслуживание и т. д. Каждая роль имеет свои определенные разрешения, а разрешения добавляются и удаляются с помощью ролей. При добавлении пользователя таким способом нам нужно только назначить ему роль, и он может иметь все разрешения роли. При изменении разрешений роли права пользователей, принадлежащих к этой роли, будут соответствующим образом изменены.

существуетcasbinиспользуется вRBACМодель нужно добавить в файл моделиrole_definitionМодуль:

[role_definition]
g = _, _

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

g = _,_Определяет отношение пользователь-роль, отображение роли-роли, первый является членом второго и имеет разрешения последнего. Тогда в матчере нам не нужно судитьr.subиp.subПолный равный, просто нужно использоватьg(r.sub, p.sub)определить предмет запросаr.subПринадлежит ли этоp.subЭта роль подойдет. Наконец, мы модифицируем файл политики, чтобы добавить определение роли пользователя:

p, admin, data, read
p, admin, data, write
p, developer, data, read
g, dajun, admin
g, lizi, developer

вышеpolicy.csvВ документе указывается,dajunпринадлежатьadminадминистратор,liziпринадлежатьdeveloperразработчики, используйтеgопределить это отношение. Кроме тогоadminк даннымdataиспользоватьreadиwriteРазрешения,developerк даннымdataТолькоreadразрешения.

package main

import (
  "fmt"
  "log"

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

func check(e *casbin.Enforcer, sub, obj, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s\n", sub, act, obj)
  } else {
    fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "data", "read")
  check(e, "dajun", "data", "write")
  check(e, "lizi", "data", "read")
  check(e, "lizi", "data", "write")
}

очевидноliziнет ролиwriteРазрешения:

dajun CAN read data
dajun CAN write data
lizi CAN read data
lizi CANNOT write data

несколькоRBAC

casbinПоддержка одновременного существования несколькихRBACСистемы, т.е. пользователи и ресурсы, имеют роли:

[role_definition]
g=_,_
g2=_,_

[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act

Приведенный выше файл модели определяет дваRBACсистемаgиg2, мы используем в матчереg(r.sub, p.sub)Определить принадлежность предмета запроса к определенной группе,g2(r.obj, p.obj)Судя по тому, что запрошенный ресурс принадлежит к определенной группе и операция согласована, его можно освободить.

Политический файл:

p, admin, prod, read
p, admin, prod, write
p, admin, dev, read
p, admin, dev, write
p, developer, dev, read
p, developer, dev, write
p, developer, prod, read
g, dajun, admin
g, lizi, developer
g2, prod.data, prod
g2, dev.data, dev

Посмотрите сначала на ролевые отношения, то есть на последние 4 строки,dajunпринадлежатьadminРоль,liziпринадлежатьdeveloperРоль,prod.dataпродуктивные ресурсыprodРоль,dev.dataресурсы развитияdevРоль.adminроль имеет паруprodиdevправа на чтение и запись для ресурсов класса,developerтолько имеют правоdevправа на чтение и запись иprodразрешение на чтение.

check(e, "dajun", "prod.data", "read")
check(e, "dajun", "prod.data", "write")
check(e, "lizi", "dev.data", "read")
check(e, "lizi", "dev.data", "write")
check(e, "lizi", "prod.data", "write")

в первой функцииe.Enforce()Метод получается первым, когда он фактически выполняетсяdajunсобственная рольadmin, затем получитьprod.dataсобственная рольprod, согласно первой строке в файлеp, admin, prod, readРазрешить запрос. в последней функцииliziпринадлежать к ролиdeveloperprod.dataпринадлежать к ролиprod, все политики запрещают это, поэтому запрос отклоняется:

dajun CAN read prod.data
dajun CAN write prod.data
lizi CAN read dev.data
lizi CAN write dev.data
lizi CANNOT write prod.data

многослойные роли

casbinТакже возможно определить роль владельца для роли, чтобы реализовать многоуровневое отношение ролей, и это отношение разрешений может быть передано. НапримерdajunЭто продвинутый разработчикsenior,seinorпринадлежит разработчику, тоdajunОн также принадлежит разработчику и имеет все права разработчика. Мы можем определить разрешения, общие для разработчиков, а затем дополнительно дляseniorОпределите некоторые специальные разрешения.

Файл модели изменять не нужно, а файл политики модифицируется следующим образом:

p, senior, data, write
p, developer, data, read
g, dajun, senior
g, senior, developer
g, lizi, developer

вышеpolicy.csvфайл определяет продвинутого разработчикаseniorк даннымdataимеютwriteРазрешения, обычные разработчикиdeveloperтолько для данныхreadразрешения. в то же времяseniorСлишкомdeveloper,такseniorтакже унаследовалreadразрешения.dajunпринадлежатьsenior,такdajunправильноdataимеютreadиwriteразрешения, покаliziПринадлежатьdeveloper, для данныхdataТолькоreadразрешения.

check(e, "dajun", "data", "read")
check(e, "dajun", "data", "write")
check(e, "lizi", "data", "read")
check(e, "lizi", "data", "write")

RBAC domain

существуетcasbin, роли могут быть глобальными или конкретнымиdomain(поле) илиtenant(арендатор), что может быть просто понято какГруппа. Напримерdajunв группеtenant1Середина — это администратор с относительно высоким авторитетом вtenant2Может просто брат.

использоватьRBAC domainТребуются следующие изменения в файле модели:

[request_definition]
r = sub, dom, obj, act

[policy_definition]
p = sub, dom, obj, act

[role_definition]
g = _,_,_

[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.obj

g=_,_,_Указывает, что первый имеет промежуточную роль в последнем, используемом в сопоставленииgпринестиdom.

p, admin, tenant1, data1, read
p, admin, tenant2, data2, read
g, dajun, admin, tenant1
g, dajun, developer, tenant2

существуетtenant1, Толькоadminданные можно прочитатьdata1. существуетtenant2, Толькоadminданные можно прочитатьdata2.dajunсуществуетtenant1средний даadmin,Но когдаtenant2Китай нет.

func check(e *casbin.Enforcer, sub, domain, obj, act string) {
  ok, _ := e.Enforce(sub, domain, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s in %s\n", sub, act, obj, domain)
  } else {
    fmt.Printf("%s CANNOT %s %s in %s\n", sub, act, obj, domain)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "tenant1", "data1", "read")
  check(e, "dajun", "tenant2", "data2", "read")
}

Результат оказался неожиданным:

dajun CAN read data1 in tenant1
dajun CANNOT read data2 in tenant2

ABAC

RBACМодели полезны для реализации относительно регулярного, относительно статического управления правами. Но для особых, динамичных потребностей,RBACЭто немного немного силы. Например, у нас есть данные в разные периоды времениdataВнедрить различные элементы управления разрешения. нормальное рабочее время9:00-18:00Каждый может читать и писатьdata, в других случаях только владелец данных может читать и писать. Мы можем легко использовать этоABAC(список доступа к базе атрибутов) модель завершает:

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[matchers]
m = r.sub.Hour >= 9 && r.sub.Hour < 18 || r.sub.Name == r.obj.Owner

[policy_effect]
e = some(where (p.eft == allow))

Это правило не требует файла политики:

type Object struct {
  Name  string
  Owner string
}

type Subject struct {
  Name string
  Hour int
}

func check(e *casbin.Enforcer, sub Subject, obj Object, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s at %d:00\n", sub.Name, act, obj.Name, sub.Hour)
  } else {
    fmt.Printf("%s CANNOT %s %s at %d:00\n", sub.Name, act, obj.Name, sub.Hour)
  }
}

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  o := Object{"data", "dajun"}
  s1 := Subject{"dajun", 10}
  check(e, s1, o, "read")

  s2 := Subject{"lizi", 10}
  check(e, s2, o, "read")

  s3 := Subject{"dajun", 20}
  check(e, s3, o, "read")

  s4 := Subject{"lizi", 20}
  check(e, s4, o, "read")
}

очевидноliziсуществует20:00не можетreadданныеdata:

dajun CAN read data at 10:00
lizi CAN read data at 10:00
dajun CAN read data at 20:00
lizi CANNOT read data at 20:00

мы знаем, что вmodel.confфайл черезr.subиr.obj,r.actк доступуEnforceпараметры метода. Фактическиsub/objМожет быть структурным объектом, благодаряgovaluateСила библиотеки, мы можемmodel.confПолучить значения полей этих структур в файле. как указано вышеr.sub.Name,r.Obj.OwnerЖдать.govaluateСодержимое библиотеки можете посмотреть в моей предыдущей статье."оценка Go Daily Library".

использоватьABACМодели допускают очень гибкое управление разрешениями, но в целомRBACДостаточно.

Хранение моделей

В приведенном выше коде мы сохраняем модель в файле.casbinТакже можно динамически инициализировать модель в коде, например.get-startedПример можно переписать так:

func main() {
  m := model.NewModel()
  m.AddDef("r", "r", "sub, obj, act")
  m.AddDef("p", "p", "sub, obj, act")
  m.AddDef("e", "e", "some(where (p.eft == allow))")
  m.AddDef("m", "m", "r.sub == g.sub && r.obj == p.obj && r.act == p.act")

  a := fileadapter.NewAdapter("./policy.csv")
  e, err := casbin.NewEnforcer(m, a)
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}

Точно так же мы можем загружать модели из строк:

func main() {
  text := `
  [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 && r.obj == p.obj && r.act == p.act
  `

  m, _ := model.NewModelFromString(text)
  a := fileadapter.NewAdapter("./policy.csv")
  e, _ := casbin.NewEnforcer(m, a)

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}

Но эти два метода не рекомендуются.

Хранение политик

В предыдущих примерах мы сохранили политику вpolicy.csvв файле. Как правило, в практических приложениях хранилище файлов редко используется.casbinПоддерживает различные методы хранения в стороннем адаптереMySQL/MongoDB/Redis/EtcdИ так далее, вы также можете реализовать собственное хранилище. Полный список смотрите здесьпотрите на guest.org/docs/en/ada…. Ниже мы вводим использованиеGorm Adapter. Сначала подключитесь к базе данных и выполните следующееSQL:

CREATE DATABASE IF NOT EXISTS casbin;

USE casbin;

CREATE TABLE IF NOT EXISTS casbin_rule (
  p_type VARCHAR(100) NOT NULL,
  v0 VARCHAR(100),
  v1 VARCHAR(100),
  v2 VARCHAR(100),
  v3 VARCHAR(100),
  v4 VARCHAR(100),
  v5 VARCHAR(100)
);

INSERT INTO casbin_rule VALUES
('p', 'dajun', 'data1', 'read', '', '', ''),
('p', 'lizi', 'data2', 'write', '', '', '');

затем используйтеGorm Adapterнагрузкаpolicy,Gorm AdapterИспользовать по умолчаниюcasbinв библиотекеcasbin_ruleповерхность:

package main

import (
  "fmt"

  "github.com/casbin/casbin/v2"
  gormadapter "github.com/casbin/gorm-adapter/v2"
  _ "github.com/go-sql-driver/mysql"
)

func check(e *casbin.Enforcer, sub, obj, act string) {
  ok, _ := e.Enforce(sub, obj, act)
  if ok {
    fmt.Printf("%s CAN %s %s\n", sub, act, obj)
  } else {
    fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
  }
}

func main() {
  a, _ := gormadapter.NewAdapter("mysql", "root:12345@tcp(127.0.0.1:3306)/")
  e, _ := casbin.NewEnforcer("./model.conf", a)

  check(e, "dajun", "data1", "read")
  check(e, "lizi", "data2", "write")
  check(e, "dajun", "data1", "write")
  check(e, "dajun", "data2", "read")
}

бегать:

dajun CAN read data1
lizi CAN write data2
dajun CANNOT write data1
dajun CANNOT read data2

использовать функцию

Мы можем использовать функции в сопоставителях.casbinнекоторые встроенные функцииkeyMatch/keyMatch2/keyMatch3/keyMatch4все совпадающие пути URL,regexMatchИспользуя обычное сопоставление,ipMatchСовпадение IP-адресов. видетьпотрите на guest.org/docs/en/women…. Мы используем встроенные функции, которые можно легко направить на разделение полномочий:

[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && r.act == p.act
p, dajun, user/dajun/*, read
p, lizi, user/lizi/*, read

Разные пользователи могут получить доступ только к URL-адресам под соответствующими маршрутами:

func main() {
  e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
  if err != nil {
    log.Fatalf("NewEnforecer failed:%v\n", err)
  }

  check(e, "dajun", "user/dajun/1", "read")
  check(e, "lizi", "user/lizi/2", "read")
  check(e, "dajun", "user/lizi/1", "read")
}

вывод:

dajun CAN read user/dajun/1
lizi CAN read user/lizi/2
dajun CANNOT read user/lizi/1

Конечно, мы также можем определить наши собственные функции. Сначала определите функцию, которая возвращает bool:

func KeyMatch(key1, key2 string) bool {
  i := strings.Index(key2, "*")
  if i == -1 {
    return key1 == key2
  }

  if len(key1) > i {
    return key1[:i] == key2[:i]
  }

  return key1 == key2[:i]
}

Здесь реализовано простое регулярное совпадение, только обработка*.

Тогда используйте эту функциюinterface{}Тип оборачивает один слой:

func KeyMatchFunc(args ...interface{}) (interface{}, error) {
  name1 := args[0].(string)
  name2 := args[1].(string)

  return (bool)(KeyMatch(name1, name2)), nil
}

Затем добавил в центр сертификации, используя:

e.AddFunction("my_func", KeyMatchFunc)

Таким образом, мы можем использовать эту функцию в сопоставителе для достижения регулярного сопоставления:

[matchers]
m = r.sub == p.sub && my_func(r.obj, p.obj) && r.act == p.act

Затем мы устанавливаем файл политики какdajunПредоставить разрешения:

p, dajun, data/*, read

dajunшаблон сопоставления парdata/*файлы имеютreadразрешения.

Подтвердите это:

check(e, "dajun", "data/1", "read")
check(e, "dajun", "data/2", "read")
check(e, "dajun", "data/1", "write")
check(e, "dajun", "mydata", "read")

dajunправильноdata/1нетwriteразрешения,mydataнесовместимыйdata/*режим, ниreadРазрешения:

dajun CAN read data/1
dajun CAN read data/2
dajun CANNOT write data/1
dajun CANNOT read mydata

Суммировать

casbinМощный, простой и эффективный, а также универсальный на нескольких языках. стоит учиться.

Если вы найдете забавную и простую в использовании языковую библиотеку Go, вы можете отправить сообщение о проблеме в ежедневной библиотеке Go GitHub😄

Ссылаться на

  1. кабин GitHub:GitHub.com/Wipe Guest/Протрите тканью…
  2. официальный сайт кабина:casbin.org/
  3. Язык описания политики управления доступом на основе метамодели:Ооо, ооо.Жо скажем .org.To/HTML/2020/2...
  4. Перейти на ежедневный репозиторий GitHub:GitHub.com/Darenjun/go-of…

я

мой блог:darjun.github.io

Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~