Главная / Статьи / Картирование словаря авторизации: API-ключи, сессии, JWT, OAuth2, OIDC, SSO

Картирование словаря авторизации: API-ключи, сессии, JWT, OAuth2, OIDC, SSO

Узнайте, как API-ключи, сессии, JWT, OAuth2, OpenID Connect и SSO взаимосвязаны, рассмотрев каждый из них через один вопрос: кто осуществляет запрос и что он может делать.

2323 слов

Разработчик добавляет опцию «Войти с Google» в проект, получает обратно токен доступа и считает задачу выполненной. Затем коллега задает простой вопрос: как приложению на самом деле известно, кто этот пользователь? Часто честный ответ заключается в том, что оно этого не знает, поскольку реализована деlegированная авторизация, а не процедура входа. Термины вроде JWT, сессия, OAuth2, OIDC и SSO обычно объясняются по отдельности, и именно поэтому они сливаются в единое понятие. В этой статье каждый из них представлен на отдельной схеме, чтобы вы могли понять, какую конкретную проблему решает тот или иной инструмент в вашей технологической стеке, и выбрать подходящее средство для следующей задачи.

Два вопроса, лежащих в основе любого механизма авторизации

Почти все решения в этой области отвечают на один из двух вопросов.

  • Аутентификация отвечает на вопрос кто вы? Это процесс проверки личности, независимо от того, принадлежит ли эта личность человеку, сервису на сервере или устройству.
  • Ауторизация отвечает на вопрос что вы имеете право делать? Она происходит после установления личности и определяет, какие ресурсы и действия доступны.

HTTP уже кодирует это разделение в своих статус-кодах. Ответ 401 Unauthorized означает, что заявитель не доказал свою личность (несмотря на вводящее в заблуждение название, речь идет об аутентификации). Ответ 403 Forbidden означает, что сервер точно знает, кто совершает запрос, но все равно отклоняет его. Если ваш API возвращает код 403 из-за отсутствия токена или 401, когда у пользователя нет необходимой роли, клиенты не могут определить, следует ли запрашивать вход или отображать сообщение «доступ запрещен».

Физический аналог помогает понять суть. У входа в офис охранник проверяет вашу удостоверение сотрудника — это аутентификация. После входа ваш пропуск открывает некоторые двери, а другие — нет; это авторизация. Одна процедура подтверждает личность, другая предоставляет права доступа, и система может пройти первую проверку, но не справиться со второй. Чтобы более подробно узнать, где в коде приложения должны находиться соответствующие проверки, ознакомьтесь с статьей об аутентификации и авторизации и о том, где каждая из них должна находиться.

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

Ключи API: идентификация клиента, а не человека

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

Authorization: Bearer your-api-key-here

Другие провайдеры используют собственные заголовки, такие как X-API-Key; идея остаётся прежней. Ключ не содержит никакой информации об использователе — это непрозрачное значение, поэтому сервер должен его проверять, чтобы определить, какой аккаунт осуществляет запрос. У него нет встроенного срока действия, если только вы его не установите, и тот, у кого есть утечка ключа, имеет такие же права доступа, как и законный владелец. Поэтому ответственность за его ротацию, ограничение диапазона использования и хранение в секретном месте лежит на вас.

Существует также HTTP Basic authentication, при котором логин и пароль в формате Base64 отправляются в заголовке. Base64 представляет собой способ кодирования, а не шифрования, поэтому такой метод безопасен только при использовании TLS; кроме того, его сложно применять в чем-то кроме очень простых внутренних инструментов. API-ключи являются более распространенным и простым вариантом, они хорошо подходят, когда вы контролируете обе стороны соединения или когда сервис взимает плату и ограничивает количество запросов по аккаунту.

Типичные места использования API-ключей:

  • API моделей ИИ
  • Платежные шлюзы
  • API погодных и других данных
  • Звонки между вашими собственными сервисами

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

Сессии: состояние на стороне сервера, хранящееся в куки

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

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

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

Расходы возникают при горизонтальном масштабировании. Если несколько серверов обрабатывают трафик, им всем необходимо иметь доступ к одному и тому же хранилищу сессий, иначе пользователь, вошедший на одну из инстансов, будет выглядеть анонимным на другой. В практике это проблема хорошо решается с помощью общей инстанса Redis, но это ещё один компонент, который нужно развернуть, отслеживать и правильно настроить; к тому же неправильно настроенное хранилище сессий может стать причиной проблем в самый неподходящий момент во время выпуска обновления.

Сессии по-прежнему отлично подходят для:

  • Традиционных веб-приложений с генерацией контента на сервере
  • Панелей управления администраторами
  • Приложений, в которых мгновенное отзыв прав доступа важнее, чем отсутствие состояния

JWT: самодостаточный, подписанный и читаемый

JSON Web Token — это подписанный JSON-объект, содержащий такую информацию, как идентификатор пользователя, его роли и время истечения срока действия. Он состоит из трех фрагментов, закодированных в формате base64url и соединённых точками:

Header.Payload.Signature

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

Подпись не означает секретность

Самое распространённое заблуждение заключается в том, что JWT скрывает своё содержимое. Это не так. Стандартный JWT подписывается, а не шифруется, и любой, у кого он есть, может декодировать полезную нагрузку с помощью одного вызова в формате Base64. Никогда не помещайте пароли, личные данные, которые вы бы не показали пользователю, или внутренние секреты в поле claims под предположением, что они будут конфиденциальными. Когда происходит инцидент, связанный с JWT, часто корнём проблемы является именно это ошибочное предположение. (Согласно спецификации JWE существуют шифрованные токены, но это отдельный формат, и редко это то, что подразумевается под «JWT» в учебных материалах.)

Токены доступа и обновления

Поскольку JWT остаётся действительным до истечения срока, обычно используется два токена:

  • Токен доступа: краткосрочный, обычно от 15 минут до часа, и отправляется с каждым запросом к API.
  • Токен обновления: имеет длительный срок действия — от нескольких дней до недель, и используется только для получения нового токена доступа без необходимости повторной авторизации пользователя.
  • Храните токен обновления в куки с атрибутом HttpOnly, а не в localStorage, где любой внедренный скрипт может его прочитать. Короткий срок действия токена доступа снижает ущерб от утечки, в то время как логика аннулирования может находиться именно в токене обновления.

    JWT отлично подходят для API, мобильных клиентов, одностраничных приложений и распределенных сервисов. Они не сделали сессии устаревшими; вместо простого аннулирования они используют принцип безсостоянийности, и правильный выбор зависит от вашей архитектуры. В статье «Сессии и JWT как модели аутентификации в Node.js» подробно рассматривается этот компромисс.

    OAuth 2.0: делегированный доступ, а не вход

    OAuth 2.0 — это концепция, которую чаще всего помещают не на то место. Это рамка авторизации, а не протокол аутентификации. Её задача — позволить одному приложению получать доступ к ресурсам, находящимся у другого сервиса, от имени пользователя, при этом пользователь никогда не передаёт свой пароль.

    В распространённом сценарии авторизации через код ваше приложение перенаправляет пользователя на экран согласия провайдера, где задаётся вопрос вроде «Разрешить этому приложению просматривать файлы вашего Google Drive?». Если пользователь соглашается, провайдер возвращает ссылку с временным кодом авторизации. Ваш бэкенд обменивает этот код на токен доступа, а затем использует его для вызова API провайдера.

    Этот токен доступа представляет собой разрешение на работу с определёнными ресурсами. Он не содержит информации о том, кто является пользователем, а спецификация OAuth2 не определяет его формат и не требует, чтобы ваше приложение могло его читать. Считать появление токена доступа доказательством того, что пользователь вошёл в систему, — это классическая ошибка из начального сценария, которая приводила к реальным уязвимостям, например когда токен, выданный одному приложению, принимался другим как доказательство личности.

    OAuth2 разработан для ответа на вопросы вроде:

    • Может ли это приложение читать файлы пользователя в Drive?
    • Может ли это приложение получать доступ к репозиториям пользователя?

    Он не предназначен для ответа на вопрос: кто этот пользователь?

    OpenID Connect: слой идентификации поверх OAuth2

    Если OAuth2 указывает, к каким ресурсам может получить доступ приложение, то OpenID Connect добавляет необходимую информацию о том, кто является этим пользователем. OIDC представляет собой упрощенный слой идентификации, построенный на основе OAuth2 с повторным использованием его механизмов перенаправления и обмена токенами. Когда вы нажимаете «Войти с Google», в основе происходящего лежит OIDC, а не просто OAuth2.

    После аутентификации пользователя поставщик OIDC возвращает два токена с разными назначениями:

    • Токен ID — это JWT, описывающий пользователя: стабильный уникальный идентификатор, а также обычно такие поля, как имя и электронная почта.
    • Токен доступа — позволяет вашему приложению вызывать API от имени пользователя.

    Токен ID служит для аутентификации пользователя в вашем приложении. Токен доступа предоставляет разрешения на выполнение дальнейших действий вашим приложением. Ваш бэкенд должен проверять подпись, источник, целевую аудиторию и срок действия токена ID перед тем, как верить его утверждениям, а также связывать пользовательские аккаунты с постоянным идентификатором субъекта, а не с адресом электронной почты, который может измениться. С такой точки зрения OAuth2 сам по себе не может обеспечить вход; OIDC дополняет его.

    SSO: пользовательский опыт, реализуемый протоколами

    Механизм единого входа часто путают с протоколами, которые его реализуют. SSO представляет собой шаблон пользовательского опыта: аутентификация происходит один раз, после чего можно перемещаться между несколькими системами без повторной авторизации. Знакомым примером является Google: войдя один раз, вы можете использовать Gmail, Drive, Calendar и YouTube без дополнительной авторизации.

    Два протокола выполняют основную работу, лежащую в основе SSO:

    • SAML: основан на XML, сформировавшийся стандарт, преобладает в корпоративных средах, таких как корпоративные порталы, панели управления CRM и внутренние инструменты.
    • OpenID Connect: основан на JSON и JWT, более новый стандарт, обычно предпочитаемый для веб- и мобильных приложений.

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

    Когда кто-то просит вас «добавить SSO», первое, что нужно выяснить, — это какой протокол поддерживает их поставщик услуг идентификации. Именно этот ответ определяет выбор библиотек, параметры конфигурации и этапы тестирования.

    Выбор механизма

    Если свести всё воедино, приблизительный руководство по принятию решения выглядит следующим образом:

    • Ваш сервис вызывается другими программами, а не людьми: ключи API с ограниченным диапазоном использования и периодической заменой, либо учетные данные клиента OAuth2 в случае необходимости временных токенов.
    • Приложение, генерируемое на сервере, или панель администрирования с собственной системой входа: сессии с безопасным куки типа HttpOnly.
    • Безсостоянийные API, мобильные клиенты или несколько сервисов, проверяющих одного пользователя: токены доступа JWT с токенами обновления.
    • Ваше приложение должно работать с данными пользователя в другом сервисе: OAuth 2.0.
    • Вы хотите, чтобы пользователи входили с уже существующего аккаунта, например Google: OpenID Connect.
    • Сотрудникам нужен один вход для работы с множеством внутренних или SaaS-инструментов: единый вход через OIDC или SAML, если это требуется поставщиком идентификации.

    Эти варианты могут сочетаться. Типичный продукт может использовать OIDC для входа, затем генерировать собственные сессии или JWT, а также хранить токены доступа OAuth2 для интеграции с третьими сторонами.

    Часто задаваемые вопросы

    В чем практические различия между аутентификацией и авторизацией? Аутентификация проверяет личность и приводит к ошибке HTTP 401; авторизация проверяет права доступа и приводит к ошибке 403. Это отдельные шаги с разными кодами ошибок, и их слияние приводит к реальным уязвимостям в безопасности.

    Стоит ли использовать JWT или сессии? Сессии идеально подходят для приложений, генерируемых на сервере и способных использовать общий хранилище сессий. JWT более уместны для безсостоянийных API, мобильных клиентов и распределенных систем. Решение следует принимать на основе архитектуры и потребностей в отзыве токенов, а не на основе того, какой вариант кажется более современным.

    Для чего используется OAuth2: для аутентификации или авторизации? Для авторизации. Он позволяет приложениям получать доступ к ресурсам от имени пользователя без подтверждения его личности. Для аутентификации используйте OpenID Connect, который расширяет OAuth2 именно в этой цели.

    Основные выводы

    • Классифицируйте каждый механизм по одному вопросу: кто запрашивает? или что они могут делать?
    • Ключи API идентифицируют клиентские приложения; они не содержат информации об учетной записи пользователя и требуют собственного контроля срока действия и обновления.
    • Сессии хранят состояние на сервере, что упрощает отзыв прав, но требует общего хранилища в крупных масштабах.
    • JWT подписываются, а не шифруются; храните секреты вне полезной нагрузки, а токены обновления — вне localStorage.
    • OAuth2 предоставляет делегированный доступ; OIDC добавляет токен идентификации, который фактически авторизует пользователя.
  • SSO — это определённый подход к реализации, осуществляемый с использованием OIDC или SAML в зависимости от поставщика идентификации.
  • Большинство недопониманий в этой области, включая путаницу с опцией «Вход с Google», возникает из-за смешивания этих двух вопросов. Необходимо разделять вопросы идентификации и предоставления прав, тогда выбор между этими инструментами становится вопросом соответствия каждого из них той задаче, для решения которой он был разработан.