• аутентификация
  • авторизация
  • oauth
  • openid-connect
  • oidc
  • приложение
  • api

Защита облачных приложений с помощью OAuth 2.0 и OpenID Connect

Полное руководство по защите ваших облачных приложений с помощью OAuth 2.0 и OpenID Connect, а также о том, как предложить отличный пользовательский опыт с аутентификацией и авторизацией.

Gao
Gao
Founder

Введение

Облачные приложения - это тренд в наши дни. Хотя тип приложения может различаться (веб, мобильное, настольное и т.д.), они все имеют облачную серверную часть, которая предоставляет услуги, такие как хранение, вычисления и базы данных. Большинство из этих приложений необходимо аутентифицировать пользователей и авторизовать их для доступа к определённым ресурсам.

Хотя собственные механизмы аутентификации и авторизации возможны, безопасность стала одной из главных забот при разработке облачных приложений. К счастью, в нашей индустрии существуют проверенные стандарты, такие как OAuth 2.0 и OpenID Connect, которые помогают нам внедрять безопасную аутентификацию и авторизацию.

В этой статье предполагается следующее:

  1. У вас есть базовое понимание разработки приложений (веб, мобильные или любого другого типа).
  2. Вы слышали о концепциях аутентификации и авторизации.
  3. Вы слышали о OAuth 2.0 и OpenID Connect.

Да, "слышали" достаточно для пунктов 2 и 3. В этой статье будут использованы примеры из реального мира для пояснения концепций и иллюстрации процесса с помощью диаграмм. Давайте начнем!

OAuth 2.0 vs. OpenID Connect

Если вы знакомы с OAuth 2.0 и OpenID Connect, вы можете продолжать чтение, потому что мы рассмотрим несколько примеров из реальной жизни в этом разделе; если вы новичок в этих стандартах, продолжайте чтение - мы представим их простым способом.

OAuth 2.0

OAuth 2.0 представляет собой рамки авторизации, которые позволяют приложению получить ограниченный доступ к защищённым ресурсам на другом приложении от имени пользователя или самого приложения. Большинство популярных сервисов, таких как Google, Facebook и GitHub, используют OAuth 2.0 для социальной авторизации (например, "Войти с Google").

Например, у вас есть веб-приложение MyApp, которое хочет получить доступ к Google Drive пользователя. Вместо того чтобы просить пользователя предоставить учетные данные Google Drive, MyApp может использовать OAuth 2.0, чтобы запросить доступ к Google Drive от имени пользователя. Вот упрощенный поток:

В этом потоке MyApp никогда не видит учетные данные Google Drive пользователя. Вместо этого, оно получает токен доступа от Google, который позволяет ему получать доступ к Google Drive от имени пользователя.

В терминах OAuth 2.0: MyApp является клиентом, Google является одновременно сервером авторизации и сервером ресурсов для простоты. В реальном мире мы часто имеем отдельные серверы авторизации и ресурса, чтобы предоставить опыт единого входа (SSO). Например, Google является сервером авторизации и может иметь несколько серверов ресурсов, таких как Google Drive, Gmail и YouTube.

Заметьте, что на самом деле поток авторизации сложнее, чем это. У OAuth 2.0 есть различные типы предоставления, области действия и другие концепции, которые необходимо знать. Оставим это в стороне и перейдём к OpenID Connect.

OpenID Connect (OIDC)

OAuth 2.0 отлично подходит для авторизации, но, возможно, вы заметили, что в нём нет способа идентифицировать пользователя (то есть аутентификации). OpenID Connect является слоем идентификации поверх OAuth 2.0, который добавляет возможности аутентификации.

В приведенном выше примере MyApp должна знать, кто является пользователем до начала потока авторизации. Обратите внимание, что здесь участвуют два пользователя: пользователь MyApp и пользователь Google Drive. В данном случае MyApp должна идентифицировать пользователя своего собственного приложения.

Посмотрим простой пример, предположим, что пользователи могут войти в MyApp, используя имя пользователя и пароль:

Поскольку мы аутентифицируем пользователя нашего собственного приложения, обычно нет необходимости запрашивать разрешения, как это делал Google в потоке OAuth 2.0. Между тем, нам нужно что-то, что может идентифицировать пользователя. OpenID Connect вводит концепции, такие как ID токен и точка доступа userinfo, чтобы помочь нам в этом.

Вы можете заметить, что поставщик идентификации (IdP) является новым отдельным участником в потоке. Он такой же, как и сервер авторизации в OAuth 2.0, но для лучшей ясности мы используем термин "IdP", чтобы показать, что он отвечает за аутентификацию пользователей и управление идентичностью.

Когда ваш бизнес растет, у вас может быть несколько приложений, использующих одну и ту же базу данных пользователей. Как и в случае OAuth 2.0, OpenID Connect позволяет вам иметь единственный сервер авторизации, который может аутентифицировать пользователей для нескольких приложений. Если пользователь уже вошел в одно приложение, ему не нужно повторно вводить свои учетные данные, когда другое приложение перенаправляет его на IdP. Поток может выполняться автоматически без взаимодействия с пользователем. Это называется единым входом (SSO).

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

Типы приложений

В предыдущем разделе мы использовали в качестве примеров веб-приложения, в то время как мир более разнообразен. Для поставщика идентификаций точный язык программирования, фреймворк или платформа, которые вы используете, на самом деле не важны. На практике одним из заметных различий является то, является ли приложение публичным клиентом или частным (доверенным) клиентом:

  • Публичный клиент: Клиент, который не может сохранить свои учетные данные конфиденциальными, что означает, что владелец ресурса (пользователь) может получить к ним доступ. Например, веб-приложение, работающее в браузере (например, одностраничное приложение).
  • Частный клиент: Клиент, который имеет возможность сохранять свои учетные данные конфиденциальными, не раскрывая их пользователям (владелцам ресурса). Например, веб-приложение, работающее на сервере (например, серверное веб-приложение) или служба API.

С учетом этого давайте посмотрим, как OAuth 2.0 и OpenID Connect могут быть использованы в различных типах приложений.

"Приложение" и "клиент" можно использовать взаимозаменяемо в контексте этой статьи.

Веб-приложения, работающие на сервере

Приложение работает на сервере и предоставляет HTML-страницы пользователям. Многие популярные веб-фреймворки, такие как Express.js, Django и Ruby on Rails, относятся к этой категории; и фреймворки backend-for-frontend (BFF) типа Next.js и Nuxt.js также включены. Эти приложения обладают следующими характеристиками:

  1. Поскольку сервер допускает только частный доступ (публичные пользователи не могут увидеть код сервера или учетные данные), он считается частным клиентом.
  2. Общий поток аутентификации пользователей такой же, как тот, который мы обсудили в разделе "OpenID Connect".
  3. Приложение может использовать ID токен, выданный поставщиком идентификаций (то есть, поставщиком OpenID Connect), для идентификации пользователя и отображения контента, специфичного для пользователя.
  4. Чтобы сохранить приложение в безопасности, оно обычно использует поток кода авторизации для аутентификации пользователей и получения токенов.

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

Одностраничные приложения (SPA)

Приложение работает в браузере пользователя и взаимодействует с сервером через API. React, Angular и Vue.js - популярные фреймворки для создания SPA. Эти приложения обладают следующими характеристиками:

  1. Поскольку код приложения виден общественности, оно считается публичным клиентом.
  2. Общий поток аутентификации пользователей такой же, как тот, который мы обсудили в разделе "OpenID Connect".
  3. Приложение может использовать ID токен, выданный поставщиком идентификаций (то есть, поставщиком OpenID Connect), для идентификации пользователя и отображения контента, специфичного для пользователя.
  4. Чтобы сохранить приложение в безопасности, оно обычно использует поток кода авторизации с PKCE (Proof Key for Code Exchange) для аутентификации пользователей и получения токенов.

Обычно SPA необходимо доступ к другим службам API для получения и обновления данных. Мы обсудим это в разделе "Защита вашего API".

Мобильные приложения

Приложение работает на мобильном устройстве (iOS, Android и т.д.) и взаимодействует с сервером через API. В большинстве случаев эти приложения обладают теми же характеристиками, что и SPA.

Машина-к-машине (M2M) приложения

Машина-к-машине приложения - это клиенты, которые работают на сервере (машине) и взаимодействуют с другими серверами. Эти приложения обладают следующими характеристиками:

  1. Как и веб-приложения, работающие на сервере, M2M приложения являются частными клиентами.
  2. Приложению может не потребоваться идентификация пользователя; вместо этого, оно должно аутентифицироваться для доступа к другим службам.
  3. Приложение может использовать токен доступа, выданный поставщиком идентификаций (то есть, поставщиком OAuth 2.0), для доступа к другим службам.
  4. Чтобы сохранить приложение в безопасности, оно обычно использует поток с учетными данными клиента для получения токенов доступа.

При доступе к другим службам приложению может потребоваться предоставить токен доступа в заголовке запроса. Мы обсудим это в разделе "Защита вашего API".

Приложения, работающие на устройствах IoT

Приложение работает на устройстве IoT (например, устройства умного дома, носимые устройства и т.д.) и взаимодействует с сервером через API. Эти приложения обладают следующими характеристиками:

  1. В зависимости от возможностей устройства, оно может быть публичным или частным клиентом.
  2. Общий поток аутентификации может быть отличным от того, что мы обсудили в разделе "OpenID Connect", в зависимости от возможностей устройства. Например, некоторые устройства могут не иметь экрана для ввода пользователем своих учетных данных.
  3. Если устройству не нужно идентифицировать пользователя, оно может не нуждаться в использовании ID токенов или точек доступа userinfo; вместо этого, оно может рассматриваться как машина-к-машине (M2M) приложение.
  4. Чтобы сохранить приложение в безопасности, оно может использовать поток кода авторизации с PKCE (Proof Key for Code Exchange) для аутентификации пользователей и получения токенов или поток с учетными данными клиента для получения токенов доступа.

При взаимодействии с сервером устройству может потребоваться предоставить токен доступа в заголовке запроса. Мы обсудим это в разделе "Защита вашего API".

Защита вашего API

С помощью OpenID Connect возможно идентифицировать пользователя и получать данные, специфичные для пользователя, через ID токены или точки доступа userinfo. Этот процесс называется аутентификацией. Но вы, вероятно, не захотите раскрывать все свои ресурсы всем аутентифицированным пользователям, например, только администраторы могут получить доступ к странице управления пользователями.

Здесь вступает в игру авторизация. Напомним, что OAuth 2.0 является рамками авторизации, а OpenID Connect - это слой идентификации, находящийся поверх OAuth 2.0; что означает, что вы также можете использовать OAuth 2.0, когда OpenID Connect уже внедрён.

Вспомним пример, который мы использовали в разделе "OAuth 2.0": MyApp хочет получить доступ к Google Drive пользователя. Непрактично позволять MyApp доступ ко всем файлам пользователя в Google Drive. Вместо этого MyApp должна чётко указать, к чему она хочет получить доступ (например, доступ только для чтения к файлам в определённой папке). В терминах OAuth 2.0 это называется областью действия.

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

Когда пользователь предоставляет доступ к MyApp, сервер авторизации выдаёт токен доступа с запрошенной областью действия. Затем токен доступа отправляется на сервер ресурсов (Google Drive), чтобы получить доступ к файлам пользователя.

Естественно, мы можем заменить Google Drive нашими собственными API-сервисами. Например, MyApp необходимо получить доступ к OrderService, чтобы получить историю заказов пользователя. На этот раз, поскольку аутентификация пользователя уже выполнена поставщиком идентификаций и как MyApp, так и OrderService находятся под нашим контролем, мы можем пропустить запрос пользователя на предоставление доступа; MyApp может напрямую отправить запрос на OrderService с токеном доступа, выданным поставщиком идентификаций.

Токен доступа может содержать область scope read:order, чтобы указать, что пользователь может читать свою историю заказов.

Теперь представьте, что пользователь случайно вводит URL страницы администратора в браузере. Поскольку пользователь не является администратором, отсутствует область admin в токене доступа. OrderService отклонит запрос и вернёт сообщение об ошибке.

В этом случае OrderService может вернуть статусный код 403 Forbidden, чтобы указать, что пользователю не разрешено доступ к странице администратора.

Для машины-к-машине (M2M) приложений ни один пользователь не задействован в процессе. Приложения могут напрямую запрашивать токены доступа у поставщика идентификаций и использовать их для доступа к другим службам. Применяется та же концепция: токен доступа содержит необходимые области для доступа к ресурсам.

Дизайн авторизации

Мы можем видеть два важных аспекта, которые стоит учесть при проектировании авторизации для защиты ваших API-сервисов:

  1. Области: Определите, к чему клиент может получить доступ. Области могут быть детализированными (например, read:order, write:order) или более общими (например, order) в зависимости от ваших требований.
  2. Контроль доступа: Определите, кто может получить определённые области. Например, только администраторы могут иметь область admin.

Что касается контроля доступа, некоторые популярные подходы включают:

  • Контроль доступа на основе ролей (RBAC): Назначение ролей пользователям и определение, какие роли могут получить доступ к каким ресурсам. Например, роль администратора может получить доступ к странице администратора.
  • Контроль доступа на основе атрибутов (ABAC): Определение политик на основе атрибутов (например, отдел пользователя, местоположение и т.д.) и принятие решений о доступе на основе этих атрибутов. Например, пользователь из отдела "Инженерия" может получить доступ к странице инженерии.

Стоит отметить, что для обоих подходов стандартным методом проверки контроля доступа является проверка областей токена доступа, а не ролей или атрибутов. Роли и атрибуты могут быть очень динамичными, а области более статичны, что делает их управление намного проще.

Для детальной информации о контроле доступа вы можете обратиться к статье RBAC и ABAC: Модели контроля доступа, которые вы должны знать.

Токены доступа

Хотя мы упоминали термин "токен доступа" много раз, мы не обсуждали, как его получить. В OAuth 2.0 токен доступа выдаётся сервером авторизации (поставщиком идентификаций) после успешного прохождения потока авторизации.

Давайте посмотрим на пример с Google Drive более внимательно и предположим, что мы используем поток кода авторизации:

Есть некоторые важные этапы в потоке для выпуска токена доступа:

  • Этап 2 (Перенаправление на Google): MyApp перенаправляет пользователя на Google с запросом авторизации. Обычно этот запрос включает следующую информацию:
    • Какой клиент (MyApp) инициирует запрос
    • Какие области запрашивает MyApp
    • Куда Google должен перенаправить пользователя после завершения авторизации
  • Этап 5 (Запрос разрешения на доступ к Google Drive): Google запрашивает у пользователя разрешение предоставить доступ к MyApp. Пользователь может выбрать предоставить или отказаться от доступа. Этот этап называется согласием.
  • Этап 7 (Перенаправить на MyApp с данными авторизации): Этот этап вновь введен в диаграмму. Вместо непосредственной выдачи токена доступа, Google возвращает однократный код авторизации MyApp для более безопасного обмена. Этот код используется для получения токена доступа.
  • Этап 8 (Использовать код авторизации для обмена на токен доступа): Это также новый этап. MyApp отправляет код авторизации в Google для обмена на токен доступа. Как поставщик идентификаций, Google составит контекст запроса и решит выдавать ли токен доступа:
    • Клиент (MyApp) - это действительно то, чем он утверждает быть
    • Пользователь предоставил доступ клиенту
    • Пользователь является тем, кем утверждает быть
    • У пользователя есть необходимые области
    • Код авторизации действителен и не истёк

В приведенном выше примере предполагается, что сервер авторизации (поставщик идентификаций) и сервер ресурса - это одно и то же (Google). Если они разделены, берём пример с MyApp и OrderService, поток будет выглядеть так:

В этом потоке сервер авторизации (IdP) выдаёт как ID токен, так и токен доступа MyApp (этап 8). ID токен используется для идентификации пользователя, а токен доступа используется для доступа к другим службам, например, OrderService. Поскольку как MyApp, так и OrderService являются собственными службами, обычно они не запрашивают у пользователя предоставление доступа; вместо этого они полагаются на контроль доступа в поставщике идентификаций, чтобы определить, может ли пользователь получить доступ к ресурсам (то есть, содержит ли токен доступа необходимые области).

Наконец, давайте посмотрим, как используется токен доступа в приложениях машина-к-машине (M2M). Поскольку ни один пользователь не задействован в процессе и приложение доверено, оно может напрямую запрашивать токен доступа у поставщика идентификаций:

Контроль доступа может всё ещё применяться здесь. Для OrderService не важно, кто пользователь или какое приложение запрашивает данные; важно только токен доступа и содержащиеся в нём области.

Токены доступа обычно кодируются как JSON Web Tokens (JWT). Чтобы узнать больше о JWT, вы можете обратиться к статье Что такое JSON Web Token (JWT)?.

Индикаторы ресурсов

OAuth 2.0 вводит концепцию областей для контроля доступа. Однако вы можете быстро осознать, что областей недостаточно:

  • OpenID Connect определяет набор стандартных областей, таких как openid, offline_access и profile. Возможно, это будет путать смешение этих стандартных областей с вашими пользовательскими областями.
  • У вас может быть несколько API-сервисов, которые используют одинаковое имя области, но имеют разный смысл.

Одним из распространённых решений является добавление суффиксов (или префиксов) к именам областей, чтобы указать ресурс (API-сервис). Например, read:order и read:product более чёткие, чем read и read. Расширение OAuth 2.0 RFC 8707 вводит новый параметр resource для указания сервера ресурсов, который клиент хочет получить.

На практике API-сервисы обычно определяются URL-адресами, поэтому более естественно использовать URL-адреса как индикаторы ресурсов. Например, API OrderService может быть представлен как:

Как видите, этот параметр удобен для использования фактических URL-адресов ресурсов в запросах авторизации и токенах доступа. Стоит отметить, что RFC 8707 может не быть реализован всеми поставщиками идентификаций. Вам следует проверить документацию вашего поставщика идентификаций, чтобы узнать, поддерживает ли он параметр resource (Logto его поддерживает).

Вкратце, параметр resource может быть использован в запросах авторизации и токенах доступа для указания ресурса, к которому клиент хочет получить доступ.

Нет ограничений на доступность индикаторов ресурсов, т.е. индикатор ресурса не обязательно должен быть реальным URL-адресом, который указывает на API-сервис. Таким образом, название "индикатор ресурса" четко отражает его роль в процессе авторизации. Вы можете использовать виртуальные URL-адреса для представления ресурсов, которые вы хотите защитить. Например, вы можете определить виртуальный URL https://api.example.com/admin в вашем монолитном приложении, чтобы представить ресурс, к которому могут получить доступ только администраторы.

Объединяем всё вместе

На этом этапе мы рассмотрели основы OAuth 2.0 и OpenID Connect, и как использовать их в различных типах приложений и сценариях. Хотя мы обсуждали их отдельно, вы можете комбинировать их в соответствии с вашими бизнес-требованиями. Общая архитектура может быть настолько простой, как это:

Или немного более сложной:

По мере роста вашего приложения, вы увидите, что поставщик идентификаций (IdP) играет критическую роль в архитектуре; но он не относится непосредственно к вашим бизнес-целям. Хотя это отличная идея - передать его надежному поставщику, мы должны выбирать поставщика идентификаций мудро. Хороший поставщик идентификаций может значительно упростить процесс, уменьшить затраты на разработку и спасти вас от потенциальных ловушек безопасности.

Заключительные заметки

Для современных облачных приложений поставщик идентификации (или сервер авторизации) является центральным местом для аутентификации пользователей, управления идентичностью, и контроля доступа. Хотя мы обсудили множество концепций в этой статье, всё еще остаются многие нюансы, которые стоит учесть при реализации такой системы. Если вас интересует более глубокое изучение, вы можете просмотреть наш блог для более подробных статей.