# Casbin — легко о сложном в авторизации
- суббота, 22 июня 2024 г. в 00:00:13
Всем привет!
Сегодня мы поговорим о том, как сделать вашу систему авторизации надежной, гибкой и простой в управлении с помощью библиотеки Casbin. Если вы когда-нибудь задумывались о том, как настроить права доступа в своем приложении, но вас пугала сложность этого процесса, то эта статья для вас. Мы разберем основные понятия и покажем, что настройка авторизации может быть не такой уж и сложной задачей.
Casbin — это мощная и легко расширяемая библиотека для управления доступом, которая поддерживает различные модели контроля доступа. Вот несколько примеров:
RBAC (Role-Based Access Control) — модель, основанная на ролях, где права доступа назначаются ролям, а не конкретным пользователям.
ABAC (Attribute-Based Access Control) — модель, основанная на атрибутах, где решения принимаются на основе атрибутов пользователя, ресурса, действия и контекста.
ACL (Access Control List) — список управления доступом, где каждому ресурсу сопоставляется список пользователей и их прав.
Сегодня мы сосредоточим наше внимание на RBAC, одной из самых популярных и понятных моделей.
RBAC (Role-Based Access Control), или управление доступом на основе ролей, — это модель контроля доступа, в которой права доступа назначаются не конкретным пользователям, а ролям. Пользователи получают права, принимая на себя определенные роли. Эта модель значительно упрощает управление правами доступа в системе, особенно когда пользователей много и их права часто меняются.
Визуальное представление RBAC (я не разобрался как в md
редакторе на забре вставить диаграмму с локальной машины xD, потому можете посмотреть диаграму на моем google drive)
https://drive.google.com/file/d/1kWD5Xds3e3jZh3fkqECBNuqvqLtRP7xu/view?usp=sharing
На диаграмме показаны:
Роли:
Роль представляет собой набор прав доступа.
Например, роль user
, которая может выполнять CRUD
с своими ресурсами и читать ресурсы других пользователей.
Пользователи:
Пользователь — это любой субъект (человек или процесс), которому нужно иметь доступ к ресурсам.
Пользователям назначаются одна или несколько ролей. Например, пользователь Bob может иметь роль user
.
Права:
Права определяют, какие действия могут выполняться над какими ресурсами.
Например, право read
может разрешать чтение данных своих или чужих постов.
Связи:
Связи между ролями и правами определяют, какие права принадлежат каким ролям.
Связи между пользователями и ролями определяют, какие роли назначены каким пользователям.
Casbin использует файл модели для определения структуры контроля доступа. Давайте создадим файл модели rbac-model.conf
:
rbac-model.conf
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.act == p.act && ; проверяем, что action совпадает как в политике, так и в кортеже, который мы получаем в request.
g(r.sub, p.sub) && ; это внутрення функция, которая сопоставляет sub с request и сравнивает с ролями пользователя в политики.
(g2(p.obj, r.obj) || p.obj == "*") ; мы проверяем, что если req.obj != * (доступ ко всем ресурсам), то выполняется внутрення функция сравнения группировок ресурсов пользователя в политики.
request_definition: Определяет формат запроса (кто, что, какое действие).
policy_definition: Определяет формат политики.
role_definition: Определяет формат ролей и группировок (группы g и g2).
policy_effect: Определяет эффект политики (разрешить или запретить).
matchers: Определяет логику сопоставления запроса с политиками, включая дополнительные группировки.
По сути мы видим, что у нас есть r (role_definition)
- кортеж, который мы будем передавать с запроса, и есть p (policy_definition)
- политика, о которой мы поговорим в следующем пункте.
Мы объявляем g
и g2
(role_definition)
- это особенность RBAC
. По сути своей это всего лишь группировки, пример так же будет в блоке про политики.
g
отвечает за роли "пользователя". Например у нас есть роль owner
и у него может быть несколько видов доступных для него действий - read, edit, delete
.
g2
- это группировка для ресурсов. Допустим у пользователя Bob
может быть несколько ресурсов, которые будут храниться в группе Bob-Resources
.
Раздел policy_effect
определяет, как Casbin
должен обрабатывать результат сопоставления политики. Он отвечает за то, что происходит, когда один или несколько правил политики применяются к запросу на доступ. Проще говоря, он решает, разрешить или запретить действие, основываясь на правилах, которые были найдены. Таким образом, policy_effect
определяет, что если хотя бы одно правило позволяет доступ, то запрос будет одобрен.
Ну и сам алгоритм проверки - matchers
. Их мы можем настраивать как нашей душе угодно, но в примере приведен пример для RBAC
модели.
Примечание Так же стоит заметить порядок сравнений в
matchers
. Так сделано для того, чтобы увеличить скорость. В случае если у нас произойдет ошибка во время проверки на действие нам не придется выполнять лишнии функции.
Теперь создадим файл с политиками policy.csv
, в котором определим права доступа и группировки:
policy.csv
<!-- определяем пользователей, даем доступ к группе ресурса и доступ к ролям -->
p, alice, admin-res, admin
p, bob, bob-res, user
p, bob, guest-res, guest
p, clare, guest-res, guest
<!-- создаем роль crud, чтобы не дублировать имена действий -->
g, crud, create
g, crud, read
g, crud, update
g, crud, delete
<!-- выдаем ролям действия -->
g, admin, crud
g, user, crud
g, guest, read
<!-- создаем группы ресурсов -->
g2, all-resources
g2, admin-res, all-resource
g2, bob-posts
g2, bob-friends
g2, bob-groups
g2, bob-res, bob-posts
g2, bob-res, bob-groups
g2, bob-res, groups
g2, guest-res, all-resource
p: Определяют права для ролей.. Например, роль admin
имеет права crud на admin-res
.
g: Определяет отношения ролей. Например, alice
является admin
, а bob
является user
.
g2: Определяет группировки ресурсов. Например, all-resources
принадлежат группе admin-res
и guest-res
Как и было обещано в предыдущем блоке - буду объяснять что да как.
Первым делом определяются политики - их очень легко понять, По сути буквой p
мы указываем, что это будем политика. Далее мы указываем имя пользователя (в настоящем приложении это может быть токен или идентификатор), далее мы указываем ресурсы к которым наш пользователь будем иметь доступ, ну и указываем действие, которые может выполнять пользователь с ресурсами, к которым у него есть доступ.
И так, далее мы видим тот самый role_definition - g
. Мы создаем группировку действий crud
, по сути это можно воспринимать как переменную - мы создаем переменную, чтобы после ее переиспользовать. Чтобы каждому пользователю не назначать каждый раз одинаковые действий мы будем назначать ему группировку. Сейчас покажу почему это сделано именно так: вместо того, чтобы делать так
p, bob, bob-res, create
p, bob, bob-res, read
p, bob, bob-res, update
p, bob, bob-res, delete
мы создали группировку и можем назначать ее любому количеству пользователей
p, den, res, crud
p, emily, res, crud
p, frank, res, crud
Точно такая же ситуация и с группировками для ресурсов, потому что у нас могут общие ресурсы, могут быть приватные ресурсы и их может быть очень много, поэтому лучше разбить их на группы и подгруппы и выдавать конкретным пользователям.
Создадим директория для проекта
mkdir casbin-rbac
cd casbin-rbac
Инициализируем приложение и установим зависимость
go mod init casbinrbac
go get github.com/casbin/casbin/v2
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/persist/file-adapter"
)
func main() {
// так же есть возможность подключить БД адаптеры, смотреть ниже
adapter := fileadapter.NewAdapter("policy.csv")
// enforcer это основной компонент, который отвечает за
// 1. Загрузка модели и политик - загружает модель (rbac-model.conf) и (policy.csv)
// 2. Принятие решений о доступе - принимает запросы на доступ
// и сопоставляет их с политиками для принятия решения, разрешить или запретить доступ
// 3. Управление ролями и пользователями - Позволяет добавлять и удалять пользователей,
// назначать роли и управлять правами доступа
enforcer, err := casbin.NewEnforcer("rbac-model.conf", a)
var (
sub = "bob"
obj = "bob-posts"
act = "read"
)
isAuth, _ := enforcer.Enforce(sub, obj, act)
if ok {
fmt.Printf("sub %s successfully authorized to %s resource, action - %s", sub, obj, act)
} else {
fmt.Printf("sub %s not-authorized to %s resource, action - %s", sub, obj, act)
}
}
Запустим наше приложение и посмотрим что из этого вышло:
go run main.go
Будем использовать официальный инструмент
go get github.com/casbin/casbin-pg-adapter
База данны обязательно должна называться casbin и иметь таблицу сasbin_rule
Для начала подними postgres
образ в docker
:
docker run --name casbin -p 5432:5432 -e POSTGRES_PASSWORD=casbin_path -e POSTGRES_USER=casbin_user -e POSTGRES_DB=casbin -d postgres
Вы можете использовать любой инструмент для миграций, я просто оставлю sql
:
create extension if not exists "uuid-ossp";;
create table if not exists casbin_rule
(
id uuid default gen_random_uuid() primary key,
ptype text not null,
v0 text not null,
v1 text not null,
v2 text,
v3 text,
v4 text,
v5 text
);
insert into casbin_rule (ptype, v0, v1, v2) values
('p', 'alice', 'admin-res', 'admin'),
('p', 'bob', 'bob-res', 'user'),
('p', 'bob', 'guest-res', 'guest'),
('p', 'clare', 'guest-res', 'guest'),
('g', 'crud', 'create', ''),
('g', 'crud', 'read', ''),
('g', 'crud', 'update', ''),
('g', 'crud', 'delete', ''),
('g', 'admin', 'crud', ''),
('g', 'user', 'crud', ''),
('g', 'guest', 'read', ''),
('g2', 'all-resources', '', ''),
('g2', 'admin-res', 'all-resources', ''),
('g2', 'bob-posts', '', ''),
('g2', 'bob-friends', '', ''),
('g2', 'bob-groups', '', ''),
('g2', 'bob-res', 'bob-posts', ''),
('g2', 'bob-res', 'bob-friends', ''),
('g2', 'bob-res', 'bob-groups', ''),
('g2', 'guest-res', 'all-resources', ''),
Запись в таблицу нужна только для примера нашего приложения. Выполните эти миграции и можете идти дальше.
Создадим адаптер:
package adapter
import (
"os"
pgadapter "github.com/casbin/casbin-pg-adapter"
"github.com/casbin/casbin/v2/persist"
)
// Casbin.Adapter должен имплементировать интерфейс persist.Adapter,
// который должен реализовать методы для работы с политиками:
/**
type Adapter interface {
// LoadPolicy loads all policy rules from the storage.
LoadPolicy(model model.Model) error
// SavePolicy saves all policy rules to the storage.
SavePolicy(model model.Model) error
// AddPolicy adds a policy rule to the storage.
// This is part of the Auto-Save feature.
AddPolicy(sec string, ptype string, rule []string) error
// RemovePolicy removes a policy rule from the storage.
// This is part of the Auto-Save feature.
RemovePolicy(sec string, ptype string, rule []string) error
// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
// This is part of the Auto-Save feature.
RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error
}
*/
func NewPgCasbinAdapter() (persist.Adapter, error) {
dsn := os.Getenv("PG_DSN")
return pgadapter.NewAdapter(dsn)
}
Конечно же это простой пример того как проходит авторизация
Casbin позволяет легко управлять ролями и пользователями в вашей системе. С его помощью можно добавлять, удалять и изменять роли, а также назначать их пользователям. Давайте рассмотрим основные операции, которые можно выполнять для управления ролями и пользователями.
Чтобы добавить пользователя к роли, можно использовать метод AddGroupingPolicy
. Это позволяет связывать пользователей с ролями.
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist/file-adapter"
)
func main() {
e, _ := casbin.NewEnforcer("rbac-model.conf", "policy.csv")
// Добавление пользователя к роли
e.AddGroupingPolicy("charlie", "user")
}
В этом примере мы добавляем пользователя charlie
к роли user
.
Чтобы удалить пользователя из роли, можно использовать метод RemoveGroupingPolicy
.
// Удаление пользователя из роли
e.RemoveGroupingPolicy("charlie", "user")
Этот метод удалит связь между пользователем charlie
и ролью user
.
Для добавления прав к роли можно использовать метод AddPolicy
.
// Добавление прав к роли
e.AddPolicy("editor", "data2", "write")
Этот метод добавляет правило, что роль editor имеет право write на ресурс data2.
Для удаления прав у роли можно использовать метод RemovePolicy
.
// Удаление прав у роли
e.RemovePolicy("editor", "data2", "write")
Этот метод удаляет правило, что роль editor имеет право write на ресурс data2.
Вы можете проверить, к каким ролям принадлежит пользователь и какие права у роли.
// Получение ролей пользователя
roles, _ := e.GetRolesForUser("alice")
// Получение прав роли
permissions, _ := e.GetPermissionsForUser("admin")
Эти методы позволяют узнать, какие роли назначены пользователю alice
и какие права имеет роль admin
.
Давайте соберем всё вместе в одном примере.
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist/file-adapter"
)
func main() {
// Загрузка модели и политик
e, _ := casbin.NewEnforcer("rbac-model.conf", "policy.csv")
// Добавление пользователя к роли
e.AddGroupingPolicy("charlie", "user")
// Удаление пользователя из роли
e.RemoveGroupingPolicy("charlie", "user")
// Добавление прав к роли
e.AddPolicy("editor", "data2", "write")
// Удаление прав у роли
e.RemovePolicy("editor", "data2", "write")
// Получение ролей пользователя
roles, _ := e.GetRolesForUser("alice")
// Получение прав роли
permissions, _ := e.GetPermissionsForUser("admin")
}
Вот и подошел к концу небольшой экскурс по Casbin
, надеюсь, что теперь вы сможете упростить работу с доступами к ресурсам и, может быть, даже сможете принести эту идею в свою команду и получить за это некоторые плюшки.
В целом это весьма несложная тема и с ней достаточно просто разобраться. Надеюсь. что смог кому-то помочь.
Так же Casbin
поддерживает и другие языки, такие как:
php
nodejs
.net
python
rust
java
В ближайшие дни я планирую снять обучающий ролик на ютуб, там я сделаю небольшое API
и покажу на примерах как использовать ролевую модель RBAC
, поэтому если кому-то интересно такое, то предлагаю перейти в мой канал, там я в скором времени анонсирую всю информацию.