Создайте службу управления доступом на основе ролей для управления разрешениями в веб-приложении SaaS.

Этот пост может служить справочным документом, в котором освещаются некоторые из лучших практик построения вашей службы управления доступом на основе ролей.

Эта статья является продолжением моей предыдущей статьи Архитектура масштабируемого программного обеспечения как услуги. Я лишь кратко упомянул управление доступом на основе ролей (RBAC) в своей предыдущей статье. Однако в этом посте мы более подробно рассмотрим детали конструкции одного из основных компонентов в нашем дизайне SaaS: службы RBAC.

RBAC обеспечивает детальный способ управления разрешениями пользователей и организаций. Наши основные модели домена: Пользователь, Организация, Роль и Разрешение. Мы подробно рассмотрим каждую модель предметной области в разделе Схема базы данных.

Зачем использовать службу RBAC?

RBAC является важным компонентом вашего веб-приложения «Программное обеспечение как услуга» (SaaS). Он определяет права, которые ваш авторизованный пользователь имеет в вашем SaaS. Он проверяет, может ли вошедший в систему пользователь выполнять определенные действия, такие как удаление другого пользователя или доступ к набору панелей отчетов. Мы можем использовать это для обработки функций, доступных для разных уровней пользователей в вашем веб-приложении SaaS. Например, пробные пользователи могут использовать только функцию A. В отличие от нее, премиум-пользователи могут использовать функции A, B и C. Мы также можем использовать их для определения прав пользователей, таких как доступ только для чтения и доступ администратора.

Если вы используете микро-интерфейсы, служба RBAC также достаточно гибкая, чтобы ее можно было повторно использовать в микро-интерфейсах, составляющих ваш SaaS. Использование RBAC может расширить ваш SaaS за счет добавления микро-интерфейсов, не беспокоясь о воссоздании функциональности разрешений по мере роста или масштабирования SaaS.

  • Гибкость. Он может обслуживать растущих пользователей, изменять разрешения для одного пользователя или группы пользователей или назначать организациям разные уровни подписки.
  • Масштабируемость. Он масштабируется по горизонтали. Мы можем добавить больше экземпляров службы по мере необходимости для удовлетворения растущих пользователей.
  • Разделение проблем. Служба RBAC будет работать независимо. Веб-приложение или микро-интерфейс, составляющий нашу SaaS, будет вызывать API RBAC.

Требования

Функциональные требования

  • Мы можем предоставить пользователям доступ к тем функциям, которые им доступны.
  • Мы можем сгруппировать пользователей в организации. Наш RBAC должен обрабатывать подписку на уровне компании или организации.
  • Пользователи могут переключаться между организациями.
  • Администраторы должны иметь возможность управлять пользователями, ролями и разрешениями в своих организациях.

Нефункциональные требования

  • Сервис должен быть масштабируемым по горизонтали.
  • Сервис должен быть повторно использован в нескольких веб-приложениях.
  • Служба может обрабатывать запросы с большим количеством операций чтения. Ожидайте больше операций чтения, чем записи, особенно при запросе разрешений, доступных пользователю. Наш сервис всегда постарается ответить на вопрос: какие разрешения доступны авторизованному пользователю? Записи будут происходить в панели администратора, когда мы обновляем разрешения пользователей или добавляем новых пользователей.

Дизайн высокого уровня

Веб-приложение Saas использует RBAC через RESTful API или вызов gRPC.

Последовательность высокого уровня

В приведенной ниже последовательности показано, как служба RBAC с веб-приложением SaaS обрабатывает авторизацию пользователя. Мы предполагаем, что у нас есть аутентифицированный пользователь в этой последовательности.

Основная цель нашей службы RBAC - предоставить веб-приложению информацию о ролях и разрешениях. Как только веб-приложение получает значение разрешения, оно по-прежнему отвечает за решение, что делать со значением. В зависимости от ролей и разрешений веб-приложение проверит, может ли оно предоставить или отозвать доступ, а также показать или скрыть определенные функции для пользователя.

Схема базы данных

Пользователь, организация, роль и разрешение

Основные таблицы, которые мы будем использовать, предназначены для хранения пользователей, организаций, ролей и разрешений.

  • Пользователь - авторизованные пользователи. Пользователь должен принадлежать хотя бы к одной организации.
  • Организация. Мы сгруппируем пользователей в организации, чтобы назначить роли нескольким пользователям одновременно. Вместо того, чтобы назначать общие роли, такие как «freeTier», индивидуально для каждого пользователя.
  • Роль. Мы сгруппируем разрешения в роли, чтобы назначить несколько разрешений пользователям и организациям. Например, роль может быть «admin» для пользователей с правами администратора или «freeTier» для пользователей, принадлежащих к организации, у которой есть подписка уровня бесплатного пользования. Мы можем назначить роли пользователям и организациям. Роль может иметь несколько разрешений.
  • Разрешение - определяет права, которые разрешено выполнять роли. Например, значение разрешения может быть «canViewReportingDashboard» или «canRemoveUser». Мы должны назначить разрешения роли. Веб-приложение SaaS будет использовать значение разрешения, чтобы решить, какие привилегии имеет вошедший в систему пользователь.

API

Вот некоторые из основных API, которые нам понадобятся в нашем SaaS. Мы не будем рассматривать очевидные API, такие как createUser или createOrganization. Я написал приведенные ниже API с псевдокодом только в иллюстративных целях. Они не представляют собой фактический код или шаблон, который вы должны использовать.

Получить пользователей по организациям

Этот API возвращает список пользователей для Организации. Мы будем использовать этот API в панели администратора, когда хотим просмотреть список пользователей в организации, чтобы обновить их разрешения.

getUsersByOrganization(orgId) -> [{username: 'ardydedase', id: 1}, .....]

Получить роли по пользователям

Этот API возвращает роли, назначенные пользователю. Полезно для обобщения того, какие права есть у пользователя. Мы будем использовать этот API в панели администратора, когда захотим назначить разрешения для определенных ролей.

getRolesByUser(userId) -> ['admin', 'premiumAccess']

Мы можем предоставить пользователям менее детализированный привилегированный доступ с помощью ролей. Например, веб-приложение может сделать доступными функции администратора, если ему известно, что вошедший в систему пользователь имеет роль администратора.

roles = getRolesByUser(userId)
if ROLES.ADMIN in roles {
  showAdminFeatures()
}

Получить разрешения от пользователя

Этот API возвращает список разрешений, доступных пользователю. Мы будем использовать список разрешений для определения привилегий пользователя.

getPermissionsByUser(userId) -> ['canManageUser', 'canAccessBillingDashboard']

Мы можем использовать разрешения, чтобы узнать, есть ли у пользователя определенные привилегии или более детальный доступ к функции. Например, веб-приложение может отображать функцию «удалить пользователя», если оно знает, что вошедший в систему пользователь имеет разрешение «управлять пользователем».

permissions = getPermissionsByUser(userId)
if permissions.CAN_MANAGE_USER in permissions {
  showRemoveUserFeature()
}

Мы также можем создать оболочку вокруг getPermissionsByUser API и использовать следующую логику:

Получить организации по пользователям

Возвращает список организаций, к которым принадлежит пользователь.

getOrganizationsByUser(userId)

Мы будем использовать этот список организаций, чтобы показать пользователям возможность переключения между организациями, частью которых они являются.

organizations = getOrganizationsByUser(userId)
if organizations.length > 0 {
  displayOrganizationsDropdown(organizations)
} else {
  showErrorMessage()
}

Добавить пользователя в организацию

Мы должны иметь возможность добавлять пользователя в организацию с помощью панели администратора. Пользователи должны принадлежать к организации, чтобы мы могли определять их разрешения на уровне организации, например: freeTrial или premium.

addUserToOrganization(userId, organizationId)

Назначьте роль пользователю или организации

У нас должна быть возможность назначать роли индивидуально пользователю или организации.

assignRoleToUser(roleId, userId)
assignRoleToOrganization(roleId, organizationId)

Добавить или удалить разрешение для роли.

Мы должны иметь возможность назначать разрешение роли.

addPermissionToRole(permissionId, roleId)
removePermissionFromRole(permissionId, roleId)

Надежность и избыточность

Наши веб-приложения SaaS полагаются на службу RBAC, чтобы знать разрешения вошедшего в систему пользователя. В зависимости от того, как веб-приложение SaaS обрабатывает сбои RBAC, оно может быть потенциально непригодным для использования, если наша служба RBAC недоступна. Мы хотим устранить любую точку отказа в нашем сервисе.

Несколько экземпляров RBAC

У нашей службы должно быть несколько экземпляров службы RBAC, работающих в производственной среде. Где одни экземпляры обслуживают живой трафик, а другие находятся в режиме ожидания. Если с работающим экземпляром что-то пойдет не так, работоспособный резервный экземпляр изящно возьмет на себя прием активного трафика.

Репликация ведущий-ведомый

Мы будем использовать стратегию репликации master-slave для нашей базы данных. Где мы будем использовать мастер для записи; чтобы обновить наши данные о разрешениях. Каждый раз, когда наш интерфейс администратора отправляет запрос на обновление в RBAC, он отправляется нашему мастеру. Однако все операции чтения RBAC из веб-приложений Saas будут направляться на подчиненные реплики. Здесь имеет смысл иметь больше подчиненных реплик для чтения, потому что наш сервис требует большого количества операций чтения. У нас также есть несколько микро-интерфейсов в нашем SaaS, которые полагаются на RBAC для получения разрешений пользователей.

Балансировщики нагрузки

Нам понадобятся балансировщики нагрузки перед нашими экземплярами службы RBAC и нашими подчиненными репликами базы данных. Балансировщики нагрузки позволяют нашему сервису распределять трафик между экземплярами сервиса и базы данных. Мы можем добавить больше экземпляров за нашим балансировщиком нагрузки для горизонтального масштабирования по мере необходимости в будущем.

Дальнейшие оптимизации

Вот некоторые функции, которые мы можем рассмотреть при дальнейшем масштабировании нашей службы RBAC.

Кеширование

Кеширование поможет снизить нагрузку на наши серверы. Мы можем ввести кеширование «горячих» данных о разрешениях. В этом случае наиболее уместна политика наименее использованных недавно (LRU). Мы удалим наименее используемые данные о разрешениях и обслужим наиболее часто используемые. Наши службы кеширования также могут иметь свой балансировщик нагрузки.

RBAC очищает связанные кэшированные данные, когда обнаруживает, что эта часть данных обновляется из пользовательского интерфейса администратора.

У вас есть возможность переложить обязанности кэширования на веб-приложение SaaS и пользовательский интерфейс администратора RBAC. Это даст вашим клиентам возможность управлять собственным кешем RBAC.

Выделенные службы чтения и записи

Большинство SaaS, вероятно, не дойдут до этой стадии. Однако мы могли бы дополнительно оптимизировать нашу конструкцию RBAC, если бы мы гипотетически увеличили количество операций записи, то есть ввели несколько клиентов SaaS, которым требуются собственные выделенные администраторы RBAC. Здесь мы разделили услуги на две части; один для чтения, а другой для письма.

Советы по реализации

Чем более детализированы наши разрешения, тем больше трафика мы ожидаем от нашего сервиса. Я бы использовал скомпилированные языки, такие как Java, Golang или C #, для написания прикладного уровня сервиса. Однако я немного предвзято отношусь к использованию Golang, поскольку это то, что я изучал и использовал в последнее время.

Схема нашей базы данных в значительной степени исправлена, и мы не ожидаем, что она изменится. У нас также есть много взаимосвязей между нашими таблицами в нашей схеме. Старая добрая СУБД, такая как MySQL или Postgres, отлично подойдет для нашего хранилища данных.

Затем мы будем использовать Memcached или Redis для нашего кеширования.

Заключение

Если вы запускаете новый продукт без каких-либо пользователей, используйте минимум дизайна RBAC - рассмотрите стратегии высокого уровня и API. Затем пересмотрите свою архитектуру, когда вам потребуется дальнейшее масштабирование.

С другой стороны, если у вас уже есть SaaS с существующими пользователями и ожидается, что он будет продолжать расти, рассмотрите рекомендации до раздела Избыточность и надежность.

Реализуйте раздел Дальнейшие оптимизации, если они вам понадобятся в дальнейшем .

Ознакомьтесь с моими другими сообщениями об архитектуре программного обеспечения: