← назад к разделу

Представьте: вы хотите, чтобы стороннее приложение — скажем, сервис, который красиво раскладывает ваши фотографии — заглянуло в ваш фотоальбом в облаке. Самый прямой путь — дать этому приложению логин и пароль от облака. Но это плохая идея: приложение увидит ваш пароль, а значит, сможет вообще всё, а не только посмотреть фотографии. И если пароль где-то утечёт, менять его придётся сразу везде. Нужен способ выдать ограниченный доступ, который легко отозвать, и при этом не показывать пароль. Ровно это решает OAuth2. А OIDC добавляет к этому ответ на отдельный вопрос — «а кто, собственно, сейчас вошёл».

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

Почему отдавать пароль — плохая идея

Начнём с проблемы, ради которой всё это придумано. Допустим, вы всё-таки дали приложению свой пароль от облака. Что не так?

  • Приложение видит ваш пароль. А пароль — это «всё сразу»: с ним можно не только смотреть фото, но и удалять их, менять настройки, читать переписку, если она там есть. Никакой границы нет.
  • Доступ нельзя сузить. Вы хотели разрешить «только посмотреть фотографии», но пароль такого не умеет. Пароль — это либо полный вход, либо ничего.
  • Доступ нельзя забрать у одного приложения. Допустим, вы передумали и больше не доверяете этому сервису. Единственный способ его «отключить» — сменить пароль. Но тогда отвалятся и все остальные приложения и устройства, которым вы давали тот же пароль.

Видно, что проблема не в конкретном приложении, а в самом инструменте: пароль слишком «грубый». Он не умеет ни ограничивать права, ни отзываться по отдельности.

OAuth2 — это протокол делегирования доступа. «Делегировать» здесь значит: разрешить кому-то действовать от вашего имени, но строго в оговорённых рамках. Вместо пароля приложение получает токен — временный «пропуск» с ограниченными правами. Этот пропуск можно выдать узким (только чтение фотографий), он сам по себе протухает через короткое время, и его можно отозвать в любой момент, не трогая пароль.

Запомните главную мысль с самого начала: OAuth2 — это про доступ. Он отвечает на вопрос «что этому приложению разрешено делать», а не на вопрос «кто этот человек».

Четыре роли OAuth2 на аналогии с пропуском в здание

В любом сценарии OAuth2 всегда участвуют четыре стороны. Их легко запомнить на бытовой аналогии: представьте офисный бизнес-центр, в который пускают по пропускам.

  • Владелец ресурса (resource owner) — это вы, посетитель. Ресурс — это, например, переговорная комната, доступ к которой решаете именно вы. Никто, кроме владельца, не вправе разрешить вход.
  • Сервер авторизации (authorization server) — это бюро пропусков на входе. Вы показываете паспорт, бюро проверяет, кто вы, и выдаёт пропуск-карту. Именно бюро решает, что вам можно, и именно оно печатает пропуска. В реальной системе эту роль играет Keycloak.
  • Клиент (client) — это приложение, которому нужен доступ. В аналогии — курьер, которого вы пригласили занести коробку в переговорную. Курьер не владелец и сам по себе никуда не вхож; он действует только потому, что вы разрешили, и только в тех рамках, что вы задали.
  • Сервер ресурсов (resource server) — это турникет (или дверной замок) на входе в саму комнату. Турникет вас лично не знает и знать не обязан. Его задача проще: подносят пропуск — он проверяет, настоящий ли он и даёт ли он право пройти именно сюда. Подходит — пускает, не подходит — нет. В реальной системе это ваш API с данными.

Теперь соберём связку одной фразой. Владелец ресурса (вы) разрешает бюро пропусков (серверу авторизации) выдать курьеру (клиенту) пропуск (токен), а турникет (сервер ресурсов) пускает курьера по этому пропуску. Пароль (ваш паспорт) при этом видит только бюро пропусков — курьеру он не достаётся.

Что на схеме: четыре роли и кто кому что передаёт. Стрелки — это «к кому обращаются» и «что отдают».

diagram

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

Access token: пропуск, с которым ходят к API

Вернёмся к турникету. Чтобы он пускал, нужен пропуск. В OAuth2 этот пропуск называется access token (токен доступа) — это и есть та самая «ключ-карта».

Проблема, которую он решает: API не может спрашивать пароль на каждый запрос — это и небезопасно, и неудобно. Вместо этого клиент один раз получает access token у сервера авторизации и потом прикладывает его к каждому обращению к API. Обычно — в HTTP-заголовке Authorization с префиксом Bearer:

GET /albums/42/photos
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

Слово Bearer буквально значит «предъявитель»: кто принёс этот токен, того API и обслуживает (поэтому токен нельзя разбрасывать). Несколько свойств access token, которые важно понимать новичку:

  • Он короткоживущий. Обычно несколько минут (часто 5–15). Так задумано: если токен утечёт, окно для злоупотребления маленькое — он быстро протухнет сам.
  • Он несёт права (scope). Внутри токена закодировано, какие действия разрешены (про scope подробно ниже). Турникет смотрит именно на это.
  • Сервер ресурсов проверяет его сам. Если токен — это подписанный JWT, турникет проверяет подпись локально, не дёргая сервер авторизации на каждый запрос. (Бывает и другой формат — об этом в самом конце.)

И самое главное про адресата: access token предназначен серверу ресурсов — то есть API. Это пропуск «чтобы что-то делать», а не удостоверение личности. По нему нельзя надёжно узнать, кто перед вами; по нему можно лишь решить, пускать ли запрос.

Refresh token: чтобы не входить заново каждые 10 минут

Тут возникает неудобство. Если access token живёт всего 10 минут, то пользователю, по идее, пришлось бы заново вводить логин и пароль каждые 10 минут. Так пользоваться приложением невозможно.

Решение — refresh token (токен обновления). Это второй пропуск, и у него другая роль. Он живёт долго (часы, дни, иногда дольше) и хранится бережно на стороне клиента. Когда короткий access token протухает, клиент тихо, без участия пользователя, идёт к серверу авторизации и обменивает refresh token на новый access token:

POST /token
grant_type=refresh_token
refresh_token=<длинный-токен>

Зачем вообще такая пара «короткий + длинный», а не один токен:

  • access token постоянно «гуляет» по сети — он летит в каждом запросе к API. Поэтому он короткий: даже если его перехватят, он быстро обесценится.
  • refresh token почти не двигается — он нужен только в момент обновления и хранится надёжнее. Поэтому ему позволено жить долго.
  • refresh token можно отозвать на сервере авторизации. После отзыва клиент больше не сможет получать новые access token — и доступ постепенно закроется сам.

Адресат тоже отличается: refresh token предназначен серверу авторизации. Только бюро пропусков умеет его принять и выдать взамен новый пропуск. К API (на турникет) с refresh token не ходят — там его не поймут.

ID token и OIDC: ответ на вопрос «кто ты»

А теперь — важная развилка, ради которой и существует OIDC. До сих пор мы говорили только про доступ. Но обратите внимание: ни access token, ни refresh token не отвечают на вопрос «кто этот человек». Турникет и не должен знать вас лично — ему хватает действительного пропуска.

А приложению зачастую как раз нужно знать пользователя: показать «Здравствуйте, Анна», подставить аватар, привязать данные к учётной записи. Из чистого OAuth2 это надёжно не вытащить — access token формально вообще непрозрачен для клиента, его не положено «вскрывать» и читать как анкету.

Вот эту дыру и закрывает OIDC (OpenID Connect) — тонкая надстройка над OAuth2, которая добавляет аутентификацию: способ узнать и доказать, кто именно вошёл. Делает она это через третий токен.

ID token — это всегда JWT (подписанный JSON), который описывает пользователя. Внутри — набор полей, их называют claims:

{
  "iss": "https://keycloak.example.com/realms/myrealm",
  "sub": "a1b2c3d4-...",
  "aud": "my-web-app",
  "exp": 1735690000,
  "iat": 1735689700,
  "name": "Анна Иванова",
  "email": "anna@example.com"
}

Что значат ключевые поля:

  • iss (issuer) — кто выдал токен, то есть адрес сервера авторизации. По нему проверяют, что токен действительно от вашего Keycloak, а не от постороннего.
  • sub (subject) — стабильный уникальный идентификатор пользователя. Именно он, а не email, — настоящий «ключ» человека: email можно сменить, а sub остаётся прежним.
  • aud (audience) — для какого приложения этот токен предназначен. Если токен выписан не вам, принимать его нельзя.
  • exp / iat — когда токен истекает и когда был выдан.

Самое частое заблуждение новичков — путать, какой токен куда идёт. Запомните правило в одной фразе: id_token предназначен клиенту (приложению — чтобы узнать, кто вошёл), а access_token — серверу ресурсов (API — чтобы решить, пускать ли запрос). Из этого следуют два «нельзя»:

  • не отправляйте id_token в API вместо access_token — это разные пропуска для разных дверей;
  • не пытайтесь читать содержимое access_token как профиль пользователя — для личности есть id_token.

OAuth2 и OIDC: в чём же разница

Эти два слова постоянно путают, потому что они работают вместе. Разведём их раз и навсегда.

  • OAuth2 — это про авторизацию, то есть про доступ. «Этому приложению разрешено читать мои фотографии.» Инструмент — access_token. Вопрос, на который он отвечает: что можно делать.
  • OIDC — это про аутентификацию, то есть про личность. «Сейчас вошла Анна, и это подтверждено сервером авторизации.» Инструмент — id_token. Вопрос, на который он отвечает: кто ты.

OIDC не заменяет OAuth2 — он достраивается поверх него. Поток получения токенов тот же самый, просто в ответ, кроме access token, добавляется ещё и id_token со сведениями о пользователе. Когда вы видите кнопку «Войти через...» на каком-нибудь сайте — за ней почти всегда стоит OIDC, а не «голый» OAuth2.

Что на схеме: одни и те же токены, но видно, какой токен про что и кому адресован.

diagram

Простая память: доступ → access_token → API → OAuth2; личность → id_token → приложение → OIDC.

Scopes: как сузить «что именно можно»

Осталась одна важная деталь. «Дать доступ» — слишком грубо: сервису для фотографий нужно только читать альбомы, а не удалять их и тем более не лезть в платёжные данные. Как выразить такую узость?

Для этого есть scope — запрашиваемый объём прав, записанный короткими метками. При входе клиент говорит, что ему нужно, а пользователь это подтверждает (часто — на экране согласия):

scope=openid profile email albums:read

Разберём по частям:

  • openid — особый scope. Именно его наличие включает OIDC, то есть просит сервер авторизации «выдай ещё и id_token». Нет openid — значит, это просто OAuth2 без сведений о личности, и id_token вы не получите.
  • profile, email — стандартные OIDC-наборы полей: имя, email и тому подобное попадут в id_token.
  • albums:read — пример прикладного права уже для вашего API: «разрешаю читать альбомы».

Полученный access token несёт согласованные scope, и сервер ресурсов по ним решает, что разрешить. Главный принцип здесь — минимально необходимый доступ: просите ровно те scope, которые реально нужны, и ни одним больше. Чем уже пропуск, тем меньше можно натворить, если он утечёт.

Как это всё собирается в один вход

В реальности все четыре роли и три токена встречаются в одном сценарии входа — он называется Authorization Code Flow и используется в большинстве веб- и мобильных приложений. Здесь дадим только общую картину, чтобы роли встали на места:

  1. Приложение (клиент) отправляет пользователя на сервер авторизации, на страницу входа.
  2. Пользователь вводит логин и пароль там же, на сервере авторизации — приложение их не видит.
  3. Сервер авторизации возвращает приложению одноразовый код авторизации (authorization code).
  4. Приложение на своём бэкенде обменивает этот код через /token на токены: access_token, refresh_token и (если был scope openid) id_token.

Ключевая мысль: пароль вводится только на сервере авторизации, а приложение получает не пароль, а короткоживущий код, который тут же меняет на токены. Для публичных клиентов (мобильные приложения, одностраничные приложения) этот обмен дополнительно защищают механизмом PKCE, чтобы перехваченный код нельзя было использовать. Подробности — в отдельной статье про Authorization Code Flow, ссылка ниже.

Небольшое уточнение про формат access token

Иногда новички слышат «opaque token» и решают, что это какой-то четвёртый токен. Это не так. Opaque и JWT — это два формата одного и того же access token, а не разные токены:

  • JWT-формат (by-value) — токен сам несёт внутри подписанные данные, и сервер ресурсов проверяет его локально по публичному ключу (через JWKS). Сервер авторизации при этом не дёргается.
  • Opaque-формат (by-reference) — токен снаружи выглядит как бессмысленная строка-ссылка, и чтобы узнать, действителен ли он и какие у него права, сервер ресурсов спрашивает сервер авторизации (introspection).

Это отдельная ось — про то, как проверяется access token, — а не «ещё один вид токена». Ролей по-прежнему четыре, а токенов по смыслу три: access_token, refresh_token, id_token.

Коротко

  • OAuth2 даёт приложению ограниченный и отзываемый доступ без передачи пароля — это про доступ (авторизацию делегирования).
  • Четыре роли: владелец ресурса (пользователь), клиент (приложение), сервер авторизации (выдаёт токены, например Keycloak), сервер ресурсов (API, проверяет токены).
  • access_token — короткоживущий пропуск к API, летит в заголовке Authorization: Bearer, адресован серверу ресурсов.
  • refresh_token — долгоживущий, меняется на новый access_token без участия пользователя через /token, адресован серверу авторизации.
  • id_token — всегда JWT о личности пользователя, адресован клиенту; появляется только с OIDC и scope openid. В API его не шлют.
  • OIDC — надстройка над OAuth2 про аутентификацию («кто ты»); сам OAuth2 — про авторизацию («что можно»).
  • scope ограничивает объём прав; openid включает OIDC, profile/email добавляют поля о пользователе.
  • opaque и JWT — это два формата access_token (проверка по ссылке или локально по JWKS), а не отдельный токен.

Что почитать дальше

  • Authorization Code Flow подробно — зачем нужен код, шаги потока и PKCE.
  • Токены и типичные ошибки — что куда отправлять, срок жизни и частые промахи с токенами.
  • Что такое Keycloak — какую роль сервера авторизации он играет в этой схеме.
  • Интеграция со Spring Security — как API проверяет access_token на стороне сервера ресурсов.