Реализация аутентификации по токену
Эта спецификация охватывает реализацию distribution/distribution
схемы аутентификации реестра версии 2. В частности, в нем описывается схема JSON Web Token, принятая distribution/distribution
для реализации непрозрачного для клиента токена Bearer, выдаваемого службой проверки подлинности и воспринимаемого реестром.
Данный документ в значительной степени заимствован из Черновая спецификация веб-токена JSON
Получение токена на предъявителя
В этом примере клиент отправляет HTTP-запрос GET на следующий URL-адрес:
https://auth.docker.io/token?service=registry.docker.io&scope=repository:samalba/my-app:pull,push
Сервер токенов должен сначала попытаться аутентифицировать клиента, используя любые учетные данные аутентификации, предоставленные с запросом. Начиная с Docker 1.8, клиент реестра в Docker Engine поддерживает только обычную аутентификацию для данных серверов токенов. Если попытка аутентификации на сервере токенов не удалась, сервер токенов должен возвращает ответ 401 Unauthorized
, указывающий, что предоставленные учетные данные недействительны.
Требует ли сервер токенов аутентификацию, зависит от политики этого поставщика управления доступом. Некоторым запросам может потребоваться аутентификация для определения доступа (например, отправка или получение частного репозитория), в то время как другим может не потребоваться (например, получение из общедоступного репозитория).
После аутентификации клиента (который может быть просто анонимным клиентом, если не было предпринято попыток аутентификации) сервер маркеров должен затем запросить свой список управления доступом, чтобы определить, имеет ли клиент запрошенную область. В этом примере запроса, если я прошел аутентификацию как пользователь jlhawn
, токен-сервер определит, какой доступ у меня есть к репозиторию samalba/my-app
, размещенному объектом registry.docker.io
.
Как только сервер маркеров определил, какой доступ клиент имеет к ресурсам, запрошенным в параметре scope
, он выполнит пересечение набора запрошенных действий для каждого ресурса и набора действий, которые фактически были предоставлены клиенту. Если клиент имеет только подмножество запрошенного доступа, это не должно считаться ошибкой, т. к. токен-сервер не несет ответственности за указание ошибок авторизации в рамках этого рабочего процесса.
Продолжая пример запроса, сервер токенов обнаружит, что клиентский множество предоставленных прав доступа к репозиторию — [pull, push]
, который при пересечении с запрошенным доступом [pull, push]
предоставляет равный множество. Если бы было обнаружено, что предоставленный множество доступа имеет только [pull]
, тогда пересекающийся множество будет только [pull]
. Если у клиента нет доступа к репозиторию, пересекаемый множество будет пустым, []
.
Именно данный пересекающийся множество доступа помещается в возвращаемый токен.
Теперь сервер создаст веб-токен JSON для сигнатуре и возврата. Веб-токен JSON состоит из 3 основных частей:
Заголовки
Заголовок веб-токена JSON является стандартным заголовком JOSE. «Тип»? поле будет «JWT»? и он также будет содержать «водоросли»? который идентифицирует алгоритм сигнатуре, используемый для создания сигнатуре. У него также должен быть «ребенок»? поле, представляющее идентификатор ключа, который использовался для сигнатуре токена.
«Малыш»? поле должно быть в формате, совместимом с отпечатками пальцев libtrust. Такой формат можно создает, выполнив следующие шаги:
Принять закодированный в DER открытый ключ, против которого был подписан токен JWT.
Создаёт из него хэш SHA256 и усекает его до 240 бит.
Разделяет результат на 12 групп в кодировке base32 с
:
в качестве разделителя.
Вот пример заголовка JOSE для веб-токена JSON (отформатирован с пробелами для удобства чтения):
{ "typ": "JWT", "alg": "ES256", "kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6" }
Он указывает, что данный объект будет веб-токеном JSON, подписанным с использованием ключа с заданным идентификатором с использованием алгоритма сигнатуре на эллиптической кривой с использованием хэша SHA256.
Претензионный множество
Множество утверждений — это структура JSON, содержащая данные стандартные зарегистрированные поля имён утверждений:
iss
(эмитент)Эмитент токена, обычно полное доменное имя сервера авторизации.
sub
(тема)Предмет токена; имя или идентификатор клиента, который его запросил. Это поле должно быть пустым («»), если клиент не прошел аутентификацию.
aud
(Аудитория)Целевая аудитория токена; имя или идентификатор службы, которая будет проверять токен для авторизации клиента/субъекта.
exp
(срок действия)Токен следует считать действительным только до указанной даты и времени.
nbf
(не ранее)Токен не должен считаться действительным до указанной даты и времени.
iat
(выдан в)Указывает дату и время, когда сервер авторизации сгенерировал данный токен.
jti
(идентификатор JWT)Уникальный идентификатор этого токена. Может использоваться целевой аудиторией для предотвращения повторного воспроизведения токена.
Множество утверждений также будет содержать имя частного утверждения, уникальное для этой спецификации сервера авторизации:
access
Массив объектов записи доступа со следующими полями:
type
Тип ресурса, размещенного службой.
name
Имя ресурса данного типа, размещенного службой.
actions
Массив строк, указывающих действия, разрешенные для этого ресурса.
Вот пример такого набора утверждений JWT (отформатирован с пробелами для удобства чтения):
{ "iss": "auth.docker.com", "sub": "jlhawn", "aud": "registry.docker.com", "exp": 1415387315, "nbf": 1415387015, "iat": 1415387015, "jti": "tYJCO1c6cnyy7kAn0c7rKPgbV1H1bFws", "access": [ { "type": "repository", "name": "samalba/my-app", "actions": [ "pull", "push" ] } ] }
Сигнатура
Сервер авторизации создаст заголовок JOSE и множество утверждений без посторонних пробелов, т. е. заголовок JOSE, указанный выше
{"typ":"JWT","alg":"ES256","kid":"PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6"}
и множество утверждений сверху будет
{"iss":"auth.docker.com","sub":"jlhawn","aud":"registry.docker.com","exp":1415387315,"nbf":1415387015,"iat":1415387015,"jti":"tYJCO1c6cnyy7kAn0c7rKPgbV1H1bFws","access":[{"type":"repository","name":"samalba/my-app","actions":["push","pull"]}]}
Представление utf-8 этого заголовка JOSE и набора утверждений затем закодировано в base64 с безопасным URL-адресом (без завершающего буфера «=»), создавая:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0
для заголовка JOSE и
eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0
для набора требований. Данные два объединяются с помощью символа «.», что предоставляет строку:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0
Затем он используется в качестве полезной нагрузки для алгоритма сигнатуре
ES256
, указанного в заголовке JOSE и полностью описанного в Разделе 3.4 проекта спецификации веб-алгоритмов JSON (JWA)В этом примере сигнатуре будет использоваться следующий ключ ECDSA для сервера:
{ "kty": "EC", "crv": "P-256", "kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6", "d": "R7OnbfMaD5J2jl7GeE8ESo7CnHSBm_1N2k9IXYFrKJA", "x": "m7zUpx3b-zmVE5cymSs64POG9QcyEpJaYCD82-549_Q", "y": "dU3biz8sZ_8GPB-odm8Wxz3lNDr1xcAQQPQaOcr1fmc" }
Результирующая сигнатура вышеуказанной полезной нагрузки с использованием этого ключа:
QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w
Объединение всего этого вместе с символом
.
предоставляет результирующий JWT:eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w
Теперь его можно поместить в HTTP-ответ и возвращает клиенту для аутентификации в службе аудитории:
HTTP/1.1 200 OK Content-Type: application/json {"token":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w"}
Использование подписанного токена
Как только у клиента появится токен, он снова попытается выполняет запрос реестра с токеном, размещенным в заголовке HTTP Authorization
следующим образом:
Authorization: Bearer
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkJWM0Q6MkFWWjpVQjVaOktJQVA6SU5QTDo1RU42Ok40SjQ6Nk1XTzpEUktFOkJWUUs6M0ZKTDpQT1RMIn0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJCQ0NZOk9VNlo6UUVKNTpXTjJDOjJBVkM6WTdZRDpBM0xZOjQ1VVc6NE9HRDpLQUxMOkNOSjU6NUlVTCIsImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5jb20iLCJleHAiOjE0MTUzODczMTUsIm5iZiI6MTQxNTM4NzAxNSwiaWF0IjoxNDE1Mzg3MDE1LCJqdGkiOiJ0WUpDTzFjNmNueXk3a0FuMGM3cktQZ2JWMUgxYkZ3cyIsInNjb3BlIjoiamxoYXduOnJlcG9zaXRvcnk6c2FtYWxiYS9teS1hcHA6cHVzaCxwdWxsIGpsaGF3bjpuYW1lc3BhY2U6c2FtYWxiYTpwdWxsIn0.Y3zZSwaZPqy4y9oRBVRImZyv3m_S9XDHF1tWwN7mL52C_IiA73SJkWVNsvNqpJIn5h7A2F8biv_S2ppQ1lgkbw
Это также приведено в Раздел 2.1 RFC 6750: Структура авторизации OAuth 2.0: Использование токена носителя
Проверка токена
Теперь реестр должен проверяет токен, представленный пользователем, путём проверки набора утверждений внутри. Реестр будет:
Убедиться, что эмитент (утверждение
iss
) является органом, которому он доверяет.Убедиться, что реестр идентифицируется как аудитория (утверждение
aud
).Убедиться, что текущее время находится между временем утверждения
nbf
иexp
.При принудительном использовании одноразовых токенов убедиться, что значение JWT ID (заявка
jti
) ранее не отображалось.Чтобы обеспечить это, реестр может хранить запись
jti
s, которую он видел, до времени маркераexp
, чтобы предотвратить повторы токена.
Проверяет значение утверждения
access
и используйте идентифицированные ресурсы и список разрешенных действий, чтобы определить, предоставляет ли токен требуемый уровень доступа для операции, которую пытается выполняет клиент.Убедиться, что сигнатура токена действительна.
Если какое-либо из данных требований не выполняется, реестр вернёт ответ 403 Forbidden
, указывающий, что токен недействителен.
Примечание: только на этом этапе рабочего процесса может возникнуть ошибка авторизации. Токен-сервер должен не возвращать ошибки, когда у пользователя нет запрошенной авторизации. Вместо этого возвращаемый токен должен указывать любую запрошенную область действия, которая есть у клиента (пересечение запрошенного и предоставленного доступа). Если токен не обеспечивает надлежащую авторизацию, реестр вернёт соответствующую ошибку.
Ни в коем случае в этом процессе реестру не нужно обращаться к серверу авторизации. Реестр должен быть снабжен только доверенными открытыми ключами для проверки сигнатуре токена.