ABAC в микросервисах: сложная матрешка прав, простой API и никакой потери производительности

от автора

Внедрение атрибутивной модели доступа (ABAC) в крупной корпоративной системе на микросервисах — это всегда испытание для архитекторов, разработчиков и бизнес-аналитиков. ABAC — одна из самых сложных областей IAM (Identity and Access Management) в корпоративных платформах, и даже простая модель может сломать мозг и пользователям, и инженерам. Рассказываю, как я реализовал масштабируемую систему с миллионами сущностей без потери производительности и сохранили простоту API для конечного разработчика.


Какие задачи решали

  • Автономность микросервисов: каждый сервис работает только с собственными данными и схемой, при обработке запроса не делает запросов к другим сервисам.

  • Гибкость прав: должны поддерживаться права «видеть всё», права по проектам, права только к отдельным документам.

  • Динамические сценарии: замещения, делегирования, индивидуальные и временные права.

  • Секретность: поддержка секретных документов, к которым доступ могут получить только конкретные пользователи.

  • Высокая производительность: обработка и фильтрация 2+ миллионов документов за < 50 мс.

  • Все данные о правах должны быть локально у микросервиса на момент обработки запроса (никаких очередей/сервисов авторизации в рантайме).


Концепция системы прав (ABAC-матрешка)

Вся архитектура строится по принципу вложенных слоев — «матрешка»:

  1. Бизнес-роль

    • Управляется админом системы.

    • Определяет бизнес-логику (например, «Менеджер проекта»).

  2. Системные роли

    • Задают наборы полномочий (например, «Документоисполнитель», «Согласующий»).

    • Определяются бизнес-аналитиками и неизменяемы во время работы (меняется только изменением тестов и переносится как change request). Привязана к бизнес процессам.

  3. Полномочия

    • Жестко фиксированы (например, documents.view, documents.sign, documents.approver).

    • Разработчик добавляет их в коде и документации.

  4. Атрибуты полномочий

    • Определяют детали применения (например, список проектов, документов, срок действия).

    • Есть глобальные (назначаются при выдаче бизнес-роли пользователю) и локальные (выдаются динамически микросервисом).


Модель данных: матрешка и пользователь

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 и понимания всей «матрешки».

А как у вас реализованы сложные права? Делитесь кейсами и болями в комментариях!



ссылка на оригинал статьи https://habr.com/ru/articles/925612/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *