Идентификация, аутентификация, авторизация

Идентификация — это заявление о том, кем вы являетесь. В зависимости от ситуации, это может быть имя, адрес электронной почты, номер учетной записи, и так далее.

Аутентификация — предоставление доказательств, что вы на самом деле есть тот, кем идентифицировались (от слова “authentic” - истинный, подлинный). В качестве доказательства может использоваться паспорт, для подтверждения личности в банке, либо ввод пароля на сайте.

Авторизация — проверка, что вам разрешен доступ к запрашиваемому ресурсу.

Виды авторизации

  • Ролевая модель доступа (RBAC) - разграничение роли к ресурсу в зависимости от группы, к которой принадлежит пользователь. Суть подхода заключается в создании ролей, повторяющих бизнес-роли в компании, и присваивание их пользователям. На основе этих ролей проверяется возможность выполнения пользователем того или иного действия.

    Пример: В сервисе существует 2 роли - User и Admin. Для некоторых методов существуют ограничения - например, доступ на запись данных есть только у пользователя с ролью Admin.

    Eventlog UseCases.drawio.png
  • Атрибутная модель доступа (ABAC) - разграничение доступа основанное на анализе правил для атрибутов объектов или субъектов и возможных операций с ними. Основное отличие этого подхода в том, что каждая ситуация оценивается не с точки зрения роли пользователя и действия, которое он хочет совершить, а с точки зрения атрибутов, которые к ним относятся. Бизнес-правило, по сути, представляет собой набор условий, в которых различные атрибуты должны удовлетворять предъявляемым к ним требованиям.

    Например, в системе существует бизнес-правило: Менеджер отделения может нанимать сотрудника только в своё отделение. Тогда при найме сотрудника необходимо настроить проверку атрибутов:

    • Субъект.Должность == “Менеджер”

    • Действие == “Найм сотрудника”

    • Объект.Филиал == Субъект.Филиал

    То есть необходимо проверить, что действие “Найм сотрудника” пытается выполнить пользователь с должностью "Менеджер". А также что найм происходит в тот филиал, менеджером которого является пользователь.

SSO/SLO (Single Sign-On/Single Log Out)

Вводные данные

Представим, что в компании есть 2 сервиса - Сервис A и Сервис Б, в которых пользователи могут произвести вход и получить доступ к определенному функционалу, в зависимости от роли. У Сервиса А своё хранилище пользователей и их ролей, у Сервиса Б - другое.

Два сервиса 1.png

Задача: Объединить данные пользователей в двух сервисах

В один день приходит задача о необходимости объединения хранилищ, так как пользователям приходится выполнять двойную работу - регистрироваться в Сервисе А, а потом в Сервисе Б. Данные пользователей, допустим, одинаковые. Отличаются роли, но проблем с переносом возникнуть не должно. Разработчик объединяет хранилище в одно, теперь пользователь регистрируется либо в Сервисе А, либо в Сервисе Б.

Два сервиса 2.png

Задача: Необходимость интеграции

Спустя какое-то время, пользователи замечают, что они выполняют вход в двух сервисах постоянно, им хотелось бы выполнить вход один раз, а сервисы А и Б пусть сами подтягивают данные о ролях и аутентифицированных пользователях.

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

Решение: Единая точка входа

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

Два сервиса 3.png

Выводы

Данный механизм называется Single Sign-on. Также при помощи Сервиса В можно реализовать Single Log-out - т.к. пользователь закончит свою сессию в Сервисе В и его аутентификационные данные “испортятся” как в Сервисе А, так и в Сервисе Б.

OAuth 2.0 и OpenIdConnect

Что такое OAuth

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

Если вернуться к примеру с Сервисом А, Сервисом Б и Сервисом В, то сервисы А и Б будут запрашивать у Сервиса В доступ к ресурсам пользователя для построения своих авторизационных политик.

Более простой пример - на сайтах нередко можно встретить кнопки по типу “Войти через аккаунт Google”, “Войти через аккаунт Яндекс”, после чего выводится окно, где перечисляется к каким данным пользователя приложение хочет получить доступ. После подтверждения согласия, приложение получит доступ к запрошенной информации пользователя.

Untitled

Это и есть реализация протокола, так как приложение или сайт получает от внешнего сервиса информацию о пользователе.

OpenIdConnect

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

OpenId Connect (OIDC) - протокол идентификации и аутентификации, построенный на основе OAuth 2.0. Он позволяет клиентским приложениям (веб- или мобильные клиенты) проверять личность пользователя через доверенного Identity provider’а и получать базовую информацию о пользователе при помощи claim’ов в токене. Тут приведен список стандартных claims.

Кто принимает решение какие claim’ы будут в токене?

  • Identity provider сам определяет, какие claim’ы предоставлять;

  • Scope - при запросе токена, клиент также может указать какие данные ему нужны;

  • User consent - пользователь может согласиться или запретить передачу некоторых данных.

Для взаимодействия при помощи OIDC, необходимо передать scope openid, тогда, к примеру, при использовании authorization code flow в ответе от token endpoint вернется не только access_token, но и id_token, который содержит в себе только аутентификационные данные пользователя.

Предоставление данных пользователя клиенту

При помощи OAuth 2.0 клиент (приложение или сайт), получает доступ к информации пользователя (resource owner), которую предоставляет сервер авторизации.

Untitled

В данном случае сервис “Дзен” запрашивает у сервера авторизации номер телефона пользователя example.mail. Стоит отметить, что в данном примере номер телефона является опциональной информацией для сервиса “Дзен”, т.е. если пользователь example.mail не захочет предоставлять доступ к номеру телефона, то авторизация пройдёт. В отличие от электронной почты, т.к. если пользователь не захочет предоставлять сервису “Дзен” свой адрес электронной почты, авторизация завершится с ошибкой.

Получение токена

После того, как пользователь предоставит информацию клиенту, клиент получает токен. Существуют разные способы получения токена доступа (access_token), которые называются Grant’ами или Authorization flow.

Client Credentials

В ситуации, когда одному клиенту необходимо получить доступ к ресурсам другого, следует использовать Client Credentials gran type, так как сервер авторизации выдаёт токен на основании client_id (логин клиента) и client_secret (пароль клиента), который не содержит вообще никаких данных о пользователе от лица которого будет действовать клиент.

Клиент - ПО (сервис, приложение), которое запрашивает токены у сервера авторизации либо для аутентификации пользователя (запрашивает identity токен), либо для доступа к ресурсу (запрашивает access токен).

? Клиент должен быть зарегистрирован в сервере авторизации, чтобы получить доступ к данным пользователя

Сервер авторизации — это сервис, который валидирует входящие данные и принимает решение о выдаче доступа. В данном случае он принимает решение выдавать ли access token клиенту.

Access token используется для предоставления доступа к ресурсу. При помощи access token’а можно получить данные клиента для построения авторизационных политик. Обычно access token представляет собой JWT токен, который считается самодостаточным, так как payload содержит все необходимые данные (claims). В некоторых случаях можно встретить access token в виде некоего набора символов. Такой тип токена называется reference. По этой строке можно получить данные клиента, используя специальный эндпоинт connect/introspect.

Client Credentials flow.drawio.drawio.png

В данном случае клиент передаёт свой client_id и secret в обмен на access_token

Когда использовать:

  • Для приложений, действующих от своего имени (без пользователя)

  • Используется в серверных приложениях и микросервисах

Resource Owner Password Credentials

В данном случае User (Resource owner) даёт клиенту - приложению, которому необходимо действовать от лица пользователя, свои учётные данные. Клиент отправляет их серверу авторизации (сервис, который отвечает за выдачу токенов), после чего сервер авторизации в ответе отдаёт клиенту токен.

Resource owner — это человек, предоставляющий доступ к некоторой части своей учетной записи. Ресурсами в этом случае могут быть данные (фотографии, документы, контакты), услуги (размещение записи в блоге, перевод средств) или любой другой ресурс, требующий ограничения доступа. Любая система, желающая действовать от имени пользователя, должна сначала получить от него разрешение.

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

Сервис авторизации при данном flow, валидирует не только данные клиента (client_id и secret), но и учётные данные пользователя - username и password.

При данном флоу, сервер авторизации может выдать не только access token, но и два новых типа токена - identity token и refresh token.

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

Scope - это механизм, разрешающий доступ приложения к определенной области учетной записи пользователя (resource). Например, если запросить scope “email”, то сервер авторизации вернет токен, который содержит email пользователя. Через параметр scope передаются “области видимости”, к которым клиент хочет получить доступ. Строгого требования к именованию scope нет, обязательным для протокола OIDC является только openid. Многие современные серверы авторизации поддерживают следующие scope:

  • email - возвращает email аутентифицированного пользователя;

  • profile - персональные данные пользователя (например ФИО), которые передаются в identity токене;

  • phone - номер телефона;

  • address - адрес пользователя

Refresh токен - это строка, которая используется для получения нового токена доступа по истечении срока действия токена доступа. Для того, чтобы получить данный тип токена, необходимо в запрашиваемых scope’ах указать offline_access.

Resource owner password flow.drawio.png

Пользователь вводит в клиентском приложении логин и пароль (1), после чего инициирует вход в систему. Клиент отправляет на сервер авторизации запрос с данными клиента (client id, client secret) и введенные данные пользователя (2). Сервер авторизации проверяет данные клиента и пользователя (3). После успешной проверки, клиент получит токен доступа (4).

В данном запросе передаётся сам логин и пароль пользователя.

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

Untitled

Это предупреждение актуально только в контексте разговора об OAuth и OpenIdConnect. Может случится так, что между клиентским приложением и авторизационным сервером, который выдает токены, может появиться третье приложение, которое получит доступ к логину и паролю пользователя. Из-за того, что в данном флоу используются данные пользователя, не рекомендуется его использовать в браузерных (и любых других публичных) клиентах. Данный флоу рекомендуется для десктопных или консольных приложений.

Когда использовать:

  • Для доверенных приложений, где пользователь вводит учетные данные напрямую

  • Менее безопасен, рекомендуется избегать, если возможно

Implicit flow

Implicit flow.drawio.png

При помощи данного флоу можно делегировать аутентификацию пользователя на сторону сервера авторизации. Пользователя (Resource owner’а) (1) перенаправляет на страницу аутентификации на стороне сервера авторизации (2). Сервер авторизации проверяет client_id и secret клиента, после чего даёт пользователю ввести свои учётные данные (3). При успешном прохождении этапа аутентификации. Далее идёт опциональный шаг данного флоу - запрос у пользователя разрешения на предоставление клиенту данных пользователя (4). Если согласие было получено, то клиент получает access_token и id_token (5), если такой был запрошен (в запросе был передан scope profile). Не стоит путать implicit flow и resource owner password flow, т.к. в данном случае клиент не получает доступ к логину и паролю пользователя.

Когда использовать:

  • Подходит для SPA (Single Page Applications)

  • Менее безопасен, чем Authorization Code Flow

  • Не поддерживает обновление токенов

Authorization code flow

Authorization code flow.drawio.png

Данный flow отличается от imlicit тем, что появляется новый тип ответа сервера авторизации - autentification code. Код авторизации — это промежуточный токен, используемый для получения access_token’а в потоке серверного приложения. Инициируем запрос на сервер (2), который генерирует ссылку для перехода на сервер авторизации, UI получает ее (3) и осуществляет переход (4). После аутентификации пользователя (5-6), сервер авторизации инициирует обратный редирект на клиента, с authorization code (7). Далее этот код, вместе с данными клиента (client_id, client_secret, redirect_uri) (9) обменивается на access-, id- и (если был запрошен) refresh токены (11). На 10-м шаге сервер авторизации проводит проверку, что клиенту с указанным client_id был выдан именно этот authorization code. Данный флоу считается одним из самых надёжных, так как позволяет избежать появления в браузере данных пользователя (id_token), refresh токена и секрета приложения.

Когда использовать:

  • Лучше всего для веб-приложений с серверной частью

  • Обеспечивает высокую безопасность

  • Позволяет обновлять токены доступа

Ссылки

OAuth 2.0 and OpenID Connect (in plain English): OAuth 2.0 and OpenID Connect (in plain English)

Diagrams of All The OpenID Connect Flows: Diagrams of All The OpenID Connect Flows

Обзор способов и протоколов аутентификации в веб-приложениях: Обзор способов и протоколов аутентификации в веб-приложениях

What is OpenID Connect: How OpenID Connect Works - OpenID Foundation

OpenId Connect Standard Claims: Final: OpenID Connect Core 1.0 incorporating errata set 2

Комментарии (2)


  1. totsamiynixon
    08.06.2025 20:11

    Сколько я уже разрабатываю веб-сервисы, еще не разу не видел адектавно реализованого механизма аутентификации и авторизации. Возникает ощущение, что никто в компании не понимает, как использовать стандартные протоколы, включая кастомных Identity Provider-ов, например на базе того же IdentityServer4.

    Я выделил следующие типы странностей при использовании OAuth2 и OIDC:

    • Отправкаid_token в API; Вроде как этот токен часть флоу аутентификации, форма токена вполне стандартная и не подразумевает разнообразие claims достаточных, для авторизации в API;

    • Отсутсвие верификации audience и scope при передаче access_token. Т.е. фактически токен может быть получен для другого API, но при этом нормально восприниматься любым API в системе. Это как будто бы нарушает принцип наименьших привелегий и оставляет кучу дыр в безопасности;

    • Пинг-понг токенами ( access_token и id_token ) между сервисами. Это развивает идеи предыдущего пункта. Т.е. в любой API можно идти с любым токеном, который выдан корректным identity provider. Данный подход начинает играть еще более интересными красками, когда где-то в середине "партии" токен истекает.

    У меня накопились следующие вопросы по использованию OAuth2 и OIDC:

    1. Правильно ли я понимаю, что OAuth2 был придуман для реализации интеграций с другими сервисами? Т.е. реализуется сценарий делегирования, когда пользователь передает Сервису Б права доступа работы с данными в Сервисе А. Примеры пользовательских сценариев: пользователь логинится в почтовом аггрегаторе. Далее пользователь передает почтовому аггрегатору права доступа к почте пользователя используя OAuth2 + offline_access.

    2. Правильно ли я понимаю, что OIDC был придуман как замена SAML, и был реализован в качестве расширения к OAuth2?

      1. Корректен ли следующий пользовательский сценарий: пользователь нажимает на кнопку "Войти через Google" в моем веб-сервисе. Далее пользователь проходит аутентификацию в Google и идет обратный редирект в мое приложение.

        1. Далее если это классическое веб-приложение (ASP.NET Core, Ruby on Rails & etc), приложение опционально делает ассоциацию пользователя в БД с subject из id_token полученного от Google и например далее записывает состояние аутентификации в куки?

        2. Далее если это SPA (React, Angular), id_token остается в приложении и... тут мое понимание зачем это надо теряется. Возвращаемся к кейсу отправки id_token к API, что противоречит рекоммендациям OAuth2.

    3. Поясните, пожалуйста, как OAuth2 должен работать в экосистеме веб-сервиса? Предположим, если веб-сервис, в котором есть несколько приложений. Например, Client Portal (панель управления клиента) - Next.JS; Developer Portal (где клиенты могут регистрировать свои клиенты в Identity Provider и покупать лицензии на интеграции) - ASP.NET Core; и Partner Portal (где партнеры могут выставлять свои API в маркетплейс Developer Portal) - ReactJS SPA. Предположим что в системе есть свой Identity Provider, который реализует OAuth2 и OIDC.

      1. Как должны работать внутренние приложения? Каждое приложение должно иметь статический клиент в identity Provider?

      2. Нужно ли Next.JS и ASP.NET Core использовать OAuth2 и писать access_token и refresh_token в куки? Или достаточно использовать OIDC? Или комбинация?

      3. Как поступить с ReactJS SPA? Куда девать id_token после логина? Он вообще нужен в этом случае?

      4. Что насчет API? Каждое API должно иметь свой resource_id и scope-ы? Так же каждый API должен иметь список требуемых ему claims для работы (например, user.role или org.id).

      5. Значит ли это, что для каждого статического клиента нужно задать список API к которым он обращается? Этот список заранее известен, поэтому, кажется, это не должно стать проблемой?

      6. Необходимо ли проверять audience claim на стороне API? Какие последствия, если проигнорировать эту проверку?

      7. Как реализовать коммуникацию между микросервисами за пределами API Gateway? Гонять токены пользователя? Использовать client_credentials флоу? Если client_credentials флоу, то нужно ли ограничивать пермишены клиентов? Типа Profile Microservice может ходить в Subscriptions Microservice на чтение, и не может ходить в остальные сервисы? Или OAuth2 здесь вообще не нужен?

    В общем такие вот вопросы. В целом на всех проектах, на которых я работал, это сделано "как-то" и работает соответствующе. Т.е. используются OAuth2 и OIDC для приложений, которые являются часть экосистемы, но одна команда везде использует id_token , другая access_token . В целом получается кое-как используется одно ключевое свойтво этого механизма - оба токена содержат subject для идентификации пользователия и механизм валидации издателя токена для аутентификации. Поэтому выглядит так, что все работает, включая SSO. А чтобы понять,какая комбинация параметров правильная и действительно безопасная для каждого конкретного пользовательского сценария - компетенций и знаний уже не хватает. А потом в вся система пропитывается таким миксом подходов, один костыль начинает подпирать другой и вот уже переделать это кажется невыполнимой задачей. Тем боеле, когда все еще не знаешь, а как правильно-то делать :) Это надо какую-то песочницу заводить и в ней ковыряться, воспроизводить ключевые системы, ключевые флоу, желательно в паре с экспертом в безопасности, в частности в OAuth2 и OIDC. Иначе получатся те же яйца, только в профиль.


    1. totsamiynixon
      08.06.2025 20:11

      Немного расширю своим виденьем, как это возможно надо делать, поправьте меня, если неправ.

      • OAuth2 только для внешних интеграций с нашим сервисом.

        • Пользователь делегирует доступ какому-то third-party сервису;

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

      • Как альтернативу OAuth2 для обоих типов интеграций можно использовать API Keys (там свои нюансы с ротацией, но как вариант).

      • OIDC как Identity Provider для логина в другой сервис / приложение через наш сервис.

      • OIDC для предоставления SSO в нашей платформе.

        • Наши веб-приложения на ASP.NET Core / NextJS получают id_token, и сами идут в нужные микросервисы, чтобы собрать объект session и либо положить его в куки, либо положить session_id в куки, а сам session в хранилище сессий, чтобы поддерживать выход из определенных сессий. Далее все взаимодействия с внутренними микросервисами идут через это веб-приложение. Или другие варианты реализации, но суть в том, что будут использоваться secure cookie.

        • Наши SPA на ReactJS / Angular работают c BFF. BFF реализует такой же механизм, как и в предыдущем сервисе через куки и служит единой точкой запросов SPA.

        • Мобильные приложения работают так же работают с BFF. Пользователь логинится в webview, далее одно двух - session_id отправляется в мобильное приложение и хранится в зашифрованном виде; либо реализуется механизм jwt + refresh_token, с хранилищем активных refresh_tokens. Все необходимые запросы к нашему сервису приложение делает через BFF.

        • Сервисы между собой аутентифицируются по комбинации следующих параметров:

          • Изоляция сети

          • iAM: что-то типа Open Policy Agent (OPA) + Gatekeeper

        • Сервисы интегрируются с внешними сервисами по OAuth2.

        • Т.е. OAuth2 вообще никак не фигурирует во внутренних сервисах, кроме интеграций с внешним миром.

      • Альетернативно OIDC, можно реализовать механизм "попроще", когда сервер возвращает подписанный jwt на заранее сконфигугированные разрешенные callback_url, а дальше то же самое - session , session_id , secure cookie. По сути тот же OIDC (только замени id_token из флоу выше на jwt), клиенты точно так же заранее сконфигурированы и имеют callback_url. Но решение с jwt не тянет за собой ритуалов навязанных базисом OAuth2. Но тут надо подумать.

      Получается OAuth2 только для интеграций. OIDC возможно для SSO, но можно и без него через jwt (опять же, чтобы не путать карты). А в сервисы вообще эта муть не тянется, там используются соответствующие инфраструктурные инструменты. И в итоге каждый решает ту проблему, для которой был создан. Но это все в теории, на практике, конечно, надо собирать сетап в песочнице.