Внедрение атрибутивной модели доступа (ABAC) в крупной корпоративной системе на микросервисах — это всегда испытание для архитекторов, разработчиков и бизнес-аналитиков. ABAC — одна из самых сложных областей IAM (Identity and Access Management) в корпоративных платформах, и даже простая модель может сломать мозг и пользователям, и инженерам. Рассказываю, как я реализовал масштабируемую систему с миллионами сущностей без потери производительности и сохранили простоту API для конечного разработчика.
Какие задачи решали
Автономность микросервисов: каждый сервис работает только с собственными данными и схемой, при обработке запроса не делает запросов к другим сервисам.
Гибкость прав: должны поддерживаться права "видеть всё", права по проектам, права только к отдельным документам.
Динамические сценарии: замещения, делегирования, индивидуальные и временные права.
Секретность: поддержка секретных документов, к которым доступ могут получить только конкретные пользователи.
Высокая производительность: обработка и фильтрация 2+ миллионов документов за < 50 мс.
Все данные о правах должны быть локально у микросервиса на момент обработки запроса (никаких очередей/сервисов авторизации в рантайме).
Концепция системы прав (ABAC-матрешка)
Вся архитектура строится по принципу вложенных слоев — «матрешка»:
-
Бизнес-роль
Управляется админом системы.
Определяет бизнес-логику (например, "Менеджер проекта").
-
Системные роли
Задают наборы полномочий (например, "Документоисполнитель", "Согласующий").
Определяются бизнес-аналитиками и неизменяемы во время работы (меняется только изменением тестов и переносится как change request). Привязана к бизнес процессам.
-
Полномочия
Жестко фиксированы (например,
documents.view
,documents.sign
,documents.approver
).Разработчик добавляет их в коде и документации.
-
Атрибуты полномочий
Определяют детали применения (например, список проектов, документов, срок действия).
Есть глобальные (назначаются при выдаче бизнес-роли пользователю) и локальные (выдаются динамически микросервисом).
Модель данных: матрешка и пользователь
1. Модель ролей и полномочий (шаблон, назначается пользователю):
{
"business_role": "Менеджер_Alpha",
"system_roles": [
{
"name": "Документоисполнитель",
"permissions": [
{
"permission": "documents.view",
"attributes": {}
},
{
"permission": "documents.sign",
"attributes": {}
}
]
}
]
}
2. После назначения пользователю (персонализированные атрибуты):
{
"user_id": "petrov",
"business_roles": [
{
"name": "Менеджер_Alpha",
"system_roles": [
{
"name": "Документоисполнитель",
"permissions": [
{
"permission": "documents.view",
"attributes": {
"projects": [101, 102]
}
},
{
"permission": "documents.sign",
"attributes": {
"projects": [101]
}
}
]
}
]
}
]
}
То есть в атрибутах полномочия теперь указан список проектов, к которым есть доступ для каждого разрешения.
3. Локальные (динамические) права (например, права согласующего или доступ к секретному документу выдаются и читаются самим микросервисом):
[
{
"user_id": "petrov",
"permission": "documents.approver",
"attributes": {
"documents": [999, 888]
}
},
{
"user_id": "ivanov",
"permission": "documents.view",
"attributes": {
"secret": [123]
}
}
]
Здесь
"documents"
— это конкретные документы, к которым у пользователя есть право как у согласующего."secret"
— явный список документов, к которым пользователь имеет доступ, даже если это секретная сущность.
Примеры: как решаются кейсы
Задача: вывести пользователю только те документы, которые он может видеть.
Параметры для запроса
has_view_all
— булево: есть ли у пользователя правоdocuments.view_all
allowed_projects
— список проектов из атрибутовdocuments.view
allowed_documents
— список документов из локальных прав, например, где пользователь согласующийsecret_documents
— список документов, где пользователь явно имеет право видеть секретные документы
SQL-запрос
SELECT *
FROM documents
WHERE
(:has_view_all)
OR (project_id = ANY(:allowed_projects))
OR (id = ANY(:allowed_documents))
OR (id = ANY(:secret_documents))
Как это работает:
Если
has_view_all
= True, пользователь увидит все документы.Если нет — по списку разрешённых проектов.
Дополнительно — любые документы, где пользователь назначен локально (например, как согласующий или в атрибуте secret).
Пример на Python
def get_user_documents(user):
# Получаем права пользователя (например, из кэша или реплики базы)
perms = get_user_permissions(user)
has_view_all = perms.get('documents.view_all', False)
allowed_projects = perms.get('documents.view', {}).get('projects', [])
allowed_documents = get_local_permissions(user, 'documents.approver').get('documents', [])
secret_documents = get_local_permissions(user, 'documents.view').get('secret', [])
query = """
SELECT * FROM documents
WHERE
(%s)
OR (project_id = ANY(%s))
OR (id = ANY(%s))
OR (id = ANY(%s))
"""
params = (
has_view_all,
allowed_projects,
allowed_documents,
secret_documents
)
return db.execute(query, params)
Пример: назначение локальных прав (доступ к секретному документу)
def grant_secret_document_access(user_id, doc_id):
# Динамически назначаем доступ к конкретному документу с признаком secret
permission_record = {
"user_id": user_id,
"permission": "documents.view",
"attributes": {
"secret": [doc_id]
}
}
save_permission(permission_record)
Проверка на отображение меню во фронте (React)
import { usePermissions } from '@company/shared-kernel';
function Sidebar() {
const { hasPermission } = usePermissions();
return (
<nav>
{hasPermission('documents.view') && (
<MenuItem icon="documents">Документы</MenuItem>
)}
{hasPermission('documents.sign') && (
<MenuItem icon="sign">Подписать</MenuItem>
)}
</nav>
);
}
Меню строится просто по наличию полномочий в токене пользователя.
атрибуты прав на фронте не нужны
Почему система сложна внутри, но проста снаружи
Администратор работает только с бизнес-ролями и готовыми системными ролями. Для него есть документация и UI — всё прозрачно.
Бизнес-аналитик проектирует системные роли, заботится о полноте сценариев (чтобы нельзя было дать право на редактирование без права на просмотр). Но после выпуска роли они фиксируются и не меняются "на лету".
Разработчик работает только с полномочиями и атрибутами — вся сложность спрятана в библиотеке shared-kernel, и логика проверки прав упрощена до одной строки.
Единственный риск — не уйти в чрезмерно сложные связи атрибутов, не нарушить принцип "не разрешено — значит запрещено".
Заключение
ABAC — одна из самых сложных тем корпоративных систем. Даже этот пример — лишь верхушка айсберга, упрощённая для понимания. Всё приведённое — иллюстрация принципов, не продакшн-код.
Главное: при продуманной архитектуре можно реализовать очень гибкую, мощную и производительную модель прав, которая масштабируется и не мешает бизнесу и разработке.
Для конечного разработчика система выглядит максимально просто: одна библиотека, три метода, никаких ручных join и понимания всей "матрешки".
А как у вас реализованы сложные права? Делитесь кейсами и болями в комментариях!
maksim_sitnikov
а запрос от бд это эт постгрес?