Реализация JSON Web Token (RFC 7519) для OneScript: подпись и проверка токенов на алгоритмах семейства HMAC.
Подпись собрана поверх штатного ХешированиеДанных - HMAC реализован по RFC 2104, внешних компонент и нативных библиотек не требуется.
opm install jwt
#Использовать jwt
Нагрузка = Новый Структура("sub,role", "user-42", "admin");
Токен = JWT.Подписать(Нагрузка, Секрет, Новый Структура("ВремяЖизни", 3600));ВремяЖизни в секундах заполняет exp, а iat проставляется автоматически.
Проверка не выбрасывает исключений - причина отказа возвращается в результате:
Результат = JWT.Проверить(Токен, Секрет);
Если Результат.Валиден Тогда
Роль = Результат.ПолезнаяНагрузка["role"];
Иначе
Логировать(Результат.КодОшибки, Результат.Ошибка); // TOKEN_EXPIRED, ...
КонецЕсли;ПолезнаяНагрузка заполняется только для валидного токена: непроверенные данные невозможно использовать по ошибке.
Параметры = Новый Структура();
Параметры.Вставить("Издатель", "oneflag");
Параметры.Вставить("Аудитория", "dashboard");
Параметры.Вставить("Допуск", 30); // расхождение часов, секунды
Результат = JWT.Проверить(Токен, Секрет, Параметры);
Результат.ВыброситьЕслиНеВалиден();| Метод | Возвращает | Описание |
|---|---|---|
Подписать(ПолезнаяНагрузка, Секрет, Параметры = Неопределено) |
Строка |
Токен в компактной сериализации JWS |
Проверить(Токен, Секрет, Параметры = Неопределено) |
JwtVerificationResult |
Проверка подписи и утверждений |
ПроверитьАсимметричный(Токен, Проверяющий, Параметры = Неопределено) |
JwtVerificationResult |
Утверждения чужого токена, подпись которого проверяет внешний объект |
Разобрать(Токен) |
Структура |
Заголовок и нагрузка без проверки подписи |
ПоддерживаемыеАлгоритмы() |
Массив |
HS256, HS384, HS512 |
ТекущееВремя() |
Число |
Текущее время в формате NumericDate |
| Параметр | Тип | Действие |
|---|---|---|
Алгоритм |
Строка | HS256 (по умолчанию), HS384, HS512 |
ВремяЖизни |
Число | Срок жизни в секундах, заполняет exp |
ДействителенЧерез |
Число | Задержка вступления в силу, заполняет nbf |
Издатель / Аудитория / Субъект / Идентификатор |
Строка, Массив | iss, aud, sub, jti |
ДобавлятьВремяВыпуска |
Булево | Заполнять iat, по умолчанию Истина |
ИдентификаторКлюча |
Строка | Заголовок kid |
ТипТокена |
Строка | Заголовок typ, по умолчанию JWT |
ДополнительныеЗаголовки |
Структура, Соответствие | Прочие заголовки JOSE |
ТекущееВремя |
Число | Подмена текущего времени (для тестов) |
Если утверждение задано и в полезной нагрузке, и параметром, выбрасывается исключение: молчаливая перезапись слишком легко приводит к неверным токенам.
| Параметр | Тип | Действие |
|---|---|---|
Алгоритмы |
Массив | Разрешённые алгоритмы, по умолчанию все три |
ПроверятьСрок |
Булево | Проверять exp и nbf, по умолчанию Истина |
Допуск |
Число | Допустимое расхождение часов в секундах |
Издатель / Аудитория / Субъект |
Строка, Массив | Ожидаемые iss, aud, sub |
ТипТокена |
Строка | Ожидаемый typ |
ТребоватьСрок / ТребоватьДействителенС / ТребоватьВремяВыпуска |
Булево | Считать отсутствие exp / nbf / iat ошибкой |
ТекущееВремя |
Число | Подмена текущего времени (для тестов) |
Свойства Валиден, ПолезнаяНагрузка, Заголовок, КодОшибки, Ошибка; методы Утверждение(Имя, ЗначениеПоУмолчанию) и ВыброситьЕслиНеВалиден().
| Код | Причина |
|---|---|
MALFORMED |
Токен не разбирается: не три части, битый base64url или не JSON |
INVALID_SIGNATURE |
Подпись не соответствует содержимому или ключу |
TOKEN_EXPIRED |
Истёк exp |
TOKEN_NOT_ACTIVE |
Не наступил nbf |
MISSING_CLAIM |
Отсутствует утверждение, объявленное обязательным |
INVALID_ISSUER / INVALID_AUDIENCE / INVALID_SUBJECT |
iss / aud / sub не совпали с ожидаемым |
ALGORITHM_NOT_ALLOWED |
Алгоритм none либо алгоритм вне списка разрешённых |
UNSUPPORTED_ALGORITHM |
Алгоритм неизвестен библиотеке |
UNSUPPORTED_CRITICAL_HEADER |
Присутствует заголовок crit |
INVALID_TYPE |
typ не совпал с ожидаемым |
KEY_ID_MISSING |
В заголовке нет kid, выбрать ключ нечем (только асимметричная проверка) |
KEY_NOT_FOUND |
Ключ с таким kid недоступен: ключи не загрузились или их нет (только асимметричная проверка) |
VERIFIER_FAILED |
Внешний проверяющий подписи упал с исключением |
VERIFICATION_FAILED |
Внешний проверяющий отказал по причине, которой нет в этом списке |
Реализованы защиты, отсутствие которых регулярно приводит к обходу авторизации в других библиотеках:
- Алгоритм
noneзапрещён всегда, даже если он указан в списке разрешённых. - Список алгоритмов задаёт проверяющая сторона. Значению
algиз самого токена не доверяем, поэтому подмена алгоритма не проходит. - Подписи сравниваются за постоянное время - по длительности проверки нельзя подобрать корректную подпись.
- Заголовок
critотвергается: RFC 7515 требует понимать критические расширения, а библиотека их не поддерживает. - Нагрузка невалидного токена не возвращается, поэтому её нельзя случайно использовать.
Библиотека подписывает и проверяет только HMAC. Для токена, выпущенного чужой стороной, HMAC не годится в принципе: общего секрета нет, а есть открытые ключи издателя. Подпись RS256 проверяет пакет jwt-verify, а эта библиотека добавляет поверх то, чего он намеренно не делает, - ожидания принимающего сервиса:
#Использовать jwt
#Использовать jwt-verify
Проверяющий = Новый ПроверяющийJWS(Новый КэшJWKS("https://token.actions.githubusercontent.com"));
Параметры = Новый Структура();
Параметры.Вставить("Издатель", "https://token.actions.githubusercontent.com");
Параметры.Вставить("Аудитория", "https://hub.example.com");
Результат = JWT.ПроверитьАсимметричный(Токен, Проверяющий, Параметры);
Если Результат.Валиден Тогда
Репозиторий = Результат.Утверждение("repository");
КонецЕсли;Результат того же класса, что и у симметричной проверки, а коды отказа переведены в общий словарь: прикладной код разбирает исход одинаково, чем бы токен ни был подписан. Проверяющим годится любой объект с методом Проверить(Токен), возвращающим структуру с полями Успех, Код, Сообщение, Алгоритм, Заголовок и Клеймы, поэтому зависимости времени выполнения у библиотеки по-прежнему нет.
Выпускать RS256-токены библиотека не умеет: для подписи нужен закрытый ключ и полноценная реализация RSA, тогда как для проверки достаточно возведения в степень по модулю. Попытка подписать асимметричным алгоритмом вызывает исключение, а не молчаливый откат на HMAC.
Проверено на эталонных значениях:
- HMAC совпадает с тестовыми векторами RFC 4231 для SHA-256, SHA-384 и SHA-512, включая случай ключа длиннее блока.
- Подписанный токен байт в байт совпадает с эталоном jwt.io для
HS256. - Токен из RFC 7515, приложение A.1 успешно проверяется.
base64urlбез выравнивания - RFC 7515, приложение C.NumericDate(exp,nbf,iat) - секунды с 1970-01-01T00:00:00Z, RFC 7519, раздел 2.audпринимается и строкой, и массивом строк - RFC 7519, раздел 4.1.3.
opm install -l
oneunit execute -d ./tests