«Это пшеница, что в темном чулане хранится, в доме, который построил Джек»
Джек имеет права на дом, а значит и на темный чулан, а стало быть и на пшеницу. Но чтобы проверить доступ Джека к пшенице, необходимо найти, в каком она хранится чулане, и в чьем доме этот чулан. Авторизация имеет линейную сложность от глубины иерархии объектов, и это плохо, т.к. всю цепочку объектов нужно читать из базы данных или держать в кеше. Становится еще хуже, если граф объектов имеет циклы и распределен между разными серверами.
Пример
Давайте рассмотрим систему управления задачами: в ней есть проекты и задачи. Проекты образуют иерархию: у каждого проекта может быть один или несколько родительских проектов. В проектах можно задавать права доступа, которые распространяются на все подпроекты. Система распределенная: родительский проект может быть расположен на другом сервере (в компании-контрагенте, например).
Требуется спроектировать процесс авторизации запросов пользователя к проектам и задачам, а также процесс изменения этих прав.
Мне известны два существующих пути решения этой задачи, восходящий и нисходящий:
-
При использовании нисходящего пути, изменения прав доступа распространяются на все вложенные ресурсы. Например, в Windows, так сделано изменение прав директории. Этот процесс может занять длительное время, возможно придется обращаться к другим серверам. Возникнут неприятные проблемы, если один из серверов окажется недоступен или в иерархии будут циклы. Зато проверка прав осуществляется быстро,
-
При восходящем пути список прав у каждого проекта свой и изменение выполняется быстро. Но проверка должна пройти по всей цепочке наследования снизу вверх, и этот путь может быть не короче нисходящего.
Существующие схемы авторизации (DAC, RBAC, ABAC…) обрабатывают каждый запрос независимо, исходя из данных аутентификации пользователя. Но пользователь может передавать серверу информацию о предыдущих авторизациях. Например, Джек получил в Росреестре свидетельство о праве на дом, затем в БТИ на основаниии этого свидетельства ему дали справку о владении чуланом. Теперь достаточно проверить, что пшеница хранится именно в указанном в справке чулане.
Inheritance-Based Access Control (IBAC)
Эта схема авторизации работает совместно с другой, базовой. Если права на доступ к ресурсу определены непосредственно для него, то работает базовая схема, а если наследуются от другого ресурса — то работает IBAC.

-
В ResourceToken хранится URI ресурса, время выдачи, полученные права, срок жизни и список ресурсов, которые позволили получить этот токен: ResourcePath.
-
На сервере хранятся сведения о наследовании прав доступа к ресурсам: InheritanceRule и сведения об инвалидации токенов Withdraw
-
ResourceToken будем считать валидным, если
-
Он подписан закрытым ключем сервера
-
Срок жизни токена еще не прошел
-
Все ресурсы из ResourcePath отсутствуют среди Withdraw.
-
-
Авторизация доступа к ресурсу X считается успешной, если
-
Базовая схема авторизации разрешила доступ, или
-
В заголовках запроса был передан валидный ResourceToken ресурса X, или
-
В заголовках запроса был передан валидный ResourceToken ресурса Y и cуществует InheritanceRule от ресурса Y к ресурсу X
-
-
При успешной авторизации создается ResourceToken для ресурса X. ResourcePath выбирается следующим образом:
-
Если доступ авторизован посредством ResourceToken ресурса Y, то берем ResourcePath из этого токена и добавляет URI ресурса Y.
-
Если доступ авторизован посредством ResourceToken ресурса X, то берем ResourcePath из этого токена.
-
Иначе берем пустой список.
-
-
При удалении правила наследования прав доступа, необходимо добавить ресурс, который наследовал права, в список Withdraw на время максимального срока действия токена. Все токены, использовавшие этот ресурс для получения доступа к другим ресурсам, должны перестать работать.
Кажется, что этот алгоритм не очень-то и быстр: опять линейная сложность от глубины иерархии (длина ResourcePath), да еще и умноженная на длину списка Withdraw! Длина этого списка зависит от правил в системе. Например, для рассмотренной выше системы управления задач, она почти наверное равна нулю: если в системе и есть возможность перемещать проекты внутрь других проектов, то ей пользуются крайне редко. А если список Withdraw пуст, то и по ResourcePath проходить не нужно, и сложность константная.
А еще на Withdraw можно просто забить и доступ пропадет по истечению времени жизни токена, а пользователю можно сообщить, что изменение прав доступа займет 24 часа.
Возможности
Множественное наследование
Иногда появляются задачи, которые затрагивают несколько отделов компании. Приходится заводить проект, лежащий в нескольких проектах, но не всегда система позволяет это, в том числе из-за сложности авторизации. Если у проекта может быть несколько надпроектов, в реализации IBAC ничего не меняется. Даже в случае циклов (X → Y → X) проблемы скорей появятся на клиенте, чем на сервере.
Распределенные системы
Сейчас популярно отдавать проекты на аутсорс в другую компанию. Давайте сделаем систему управления задач распределенной: проекты могут храниться на разных серверах. Имея ResourceToken пользователь может получить доступ до проекта другой компании, но для этого сервера должны публиковать следующие сведения:
-
Публичный ключ для проверки подписи токенов
-
Список Withdraw для проверки ResourcePath
Все. Больше ничего менять не нужно. Список InheritanceRule хранится рядом с ресурсом, который наследует доступ (на том же сервере), поэтому легко доступен во время авторизации. Но, в этот случае придется все-таки пройти по ResourcePath и запросить Withdraw со всех необходимых серверов.
Микросервисы
В микросервисной архитектуре есть неудобство, что почти все сервисы зависят от сервиса авторизации. IBAC не удаляет эту зависимость, но ослабляет её: для проверки прав нужен только публичный ключ и список Withdraw.
Выводы
-
Спроектирована схема авторизации
-
Работает почти наверное за O(1)
-
Может быть использована в распределенных системах
-
Не боится множественного наследования и циклов
ссылка на оригинал статьи https://habr.com/ru/post/593445/
Добавить комментарий