# 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, поэтому если кому-то интересно такое, то предлагаю перейти в мой канал, там я в скором времени анонсирую всю информацию.