JWT часто встречается в REST/API-интеграциях: токен приходит от внешнего сервиса или, наоборот, его нужно сформировать на стороне 1С. В отладке обычно требуется быстро увидеть Header и Payload, проверить iss, aud, exp, понять причину ошибки подписи или собрать тестовый HS256-токен.
JWT Toolkit делает это прямо в 1С. В обработке две основные вкладки: проверка существующего JWT и создание нового JWT с подписью HS256.
Что умеет обработка
- разбирать JWT на Header, Payload и Signature;
- декодировать и кодировать Base64URL;
- показывать Header JSON и Payload JSON;
- извлекать
alg,typ,iss,sub,aud,iatиexp; - показывать срок действия токена;
- проверять подпись HS256 по secret;
- создавать новый HS256 JWT с автоматическими
iatиexp; - добавлять собственные поля Payload через JSON;
- показывать понятную диагностику для повреждённых JWT и некорректных данных.
Разбираем JWT на три части
Формат JWT — это три Base64URL-части, разделённые точками: header.payload.signature. Поэтому первый шаг — проверить структуру токена, декодировать Header и Payload и прочитать оба JSON-объекта.
Токен = СокрЛП(JWT);
Части = СтрРазделить(Токен, ".", Истина);
Если Части.Количество() <> 3 Тогда
Диагностика = "JWT должен состоять из трёх частей, разделённых точками.";
Возврат;
КонецЕсли;
ТекстHeader = ДекодироватьBase64URLВСтроку(Части[0]);
ТекстPayload = ДекодироватьBase64URLВСтроку(Части[1]);
Header = ПрочитатьJSONИзСтрокиВСоответствие(ТекстHeader);
Payload = ПрочитатьJSONИзСтрокиВСоответствие(ТекстPayload);
HeaderJSON = ТекстHeader;
PayloadJSON = ТекстPayload;
Исходные первые две части важно не пересобирать перед проверкой подписи: HS256 считается именно по строке base64url(header) + "." + base64url(payload), которая пришла в токене.
Base64URL в 1С
Обычный Base64 для JWT не подходит напрямую. В Base64URL используются - и _ вместо + и /, а завершающий padding = обычно отсутствует. При декодировании его нужно восстановить.
Функция ДекодироватьBase64URLВСтроку(Знач СтрокаBase64URL)
Если ПустаяСтрока(СтрокаBase64URL) Тогда
ВызватьИсключение "Пустая Base64URL-строка";
КонецЕсли;
СтрокаBase64 = СтрЗаменить(СтрокаBase64URL, "-", "+");
СтрокаBase64 = СтрЗаменить(СтрокаBase64, "_", "/");
Остаток = СтрДлина(СтрокаBase64) % 4;
Если Остаток = 1 Тогда
ВызватьИсключение "Некорректная длина Base64URL";
ИначеЕсли Остаток = 2 Тогда
СтрокаBase64 = СтрокаBase64 + "==";
ИначеЕсли Остаток = 3 Тогда
СтрокаBase64 = СтрокаBase64 + "=";
КонецЕсли;
ДвоичныеДанные = ПолучитьДвоичныеДанныеИзBase64Строки(СтрокаBase64);
Возврат ПолучитьСтрокуИзДвоичныхДанных(ДвоичныеДанные, КодировкаТекста.UTF8);
КонецФункции
&НаСервере
При кодировании выполняется обратная операция: обычный Base64 переводится в URL-safe вид, переводы строк и завершающие = удаляются. Эта часть используется и Inspector, и Generator.
Claims и срок действия
После чтения JSON основные claims выводятся отдельно. iat и exp хранят Unix timestamp, поэтому обработка переводит их в читаемое UTC-время, а по exp сразу определяет статус токена.
Алгоритм = ПолучитьClaimСтрокой(Header, "alg");
ТипJWT = ПолучитьClaimСтрокой(Header, "typ");
Issuer = ПолучитьClaimСтрокой(Payload, "iss");
Subject = ПолучитьClaimСтрокой(Payload, "sub");
Audience = ПолучитьClaimСтрокой(Payload, "aud");
ЗначениеIAT = Payload.Получить("iat");
Если ЗначениеIAT <> Неопределено Тогда
Выпущен = UnixTimestampВСтроку(ЗначениеIAT);
Иначе
Выпущен = "Не указан";
КонецЕсли;
ЗначениеEXP = Payload.Получить("exp");
Если ЗначениеEXP <> Неопределено Тогда
Истекает = UnixTimestampВСтроку(ЗначениеEXP);
Если ТипЗнч(ЗначениеEXP) = Тип("Число") Тогда
Если ЗначениеEXP < ТекущийUnixTimestamp() Тогда
СтатусСрока = "Истёк";
Иначе
СтатусСрока = "Действует";
КонецЕсли;
Иначе
СтатусСрока = "Поле exp не является числом";
КонецЕсли;
Иначе
Истекает = "Не указан";
СтатусСрока = "Срок не указан";
КонецЕсли;
Если exp отсутствует, это не считается ошибкой: обработка показывает отдельный статус «Срок не указан».
Как считается HMAC-SHA256
Для HS256 нужен HMAC-SHA256. В реализации используется стандартная схема HMAC с блоком SHA-256 размером 64 байта: длинный ключ сначала хешируется, затем формируются ipad и opad, после чего выполняются внутренний и внешний SHA-256.
Функция ВычислитьHMACSHA256(Знач ДанныеДляПодписи, Знач Секрет)
Ключ = ПолучитьДвоичныеДанныеИзСтроки(Секрет, КодировкаТекста.UTF8);
Данные = ПолучитьДвоичныеДанныеИзСтроки(ДанныеДляПодписи, КодировкаТекста.UTF8);
ДлинаБлока = 64;
Если Ключ.Размер() > ДлинаБлока Тогда
ХешКлюча = Новый ХешированиеДанных(ХешФункция.SHA256);
ХешКлюча.Добавить(Ключ);
КлючБуфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ХешКлюча.ХешСумма);
Иначе
КлючБуфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Ключ);
КонецЕсли;
БлокКлюча = Новый БуферДвоичныхДанных(ДлинаБлока);
БлокКлюча.Записать(0, КлючБуфер);
ВнутреннийКлюч = БлокКлюча.Скопировать();
ВнешнийКлюч = БлокКлюча;
IPad = Новый БуферДвоичныхДанных(ДлинаБлока);
OPad = Новый БуферДвоичныхДанных(ДлинаБлока);
Для Индекс = 0 По ДлинаБлока - 1 Цикл
IPad.Установить(Индекс, 54);
OPad.Установить(Индекс, 92);
КонецЦикла;
ВнутреннийКлюч.ЗаписатьПобитовоеИсключительноеИли(0, IPad);
ВнешнийКлюч.ЗаписатьПобитовоеИсключительноеИли(0, OPad);
ВнутреннийХеш = Новый ХешированиеДанных(ХешФункция.SHA256);
ВнутреннийХеш.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(ВнутреннийКлюч));
ВнутреннийХеш.Добавить(Данные);
ВнешнийХеш = Новый ХешированиеДанных(ХешФункция.SHA256);
ВнешнийХеш.Добавить(ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(ВнешнийКлюч));
ВнешнийХеш.Добавить(ВнутреннийХеш.ХешСумма);
Возврат ВнешнийХеш.ХешСумма;
КонецФункции
Это центральная часть HS256: на вход функция получает строку header.payload и secret в UTF-8, а на выходе возвращает двоичную SHA-256 подпись. Затем она кодируется через Base64URL.
Проверяем подпись HS256
Перед проверкой обработка смотрит alg. В версии 1.0 подпись проверяется только для HS256; JWT с другим алгоритмом всё равно можно разобрать и посмотреть.
Если ВРег(Алгоритм) <> "HS256" Тогда
СтатусПодписи = "Не поддерживается";
Диагностика = "Проверка подписи в версии 1.0 поддерживается только для HS256.";
Возврат;
КонецЕсли;
Части = СтрРазделить(СокрЛП(JWT), ".", Истина);
ДанныеДляПодписи = Части[0] + "." + Части[1];
ОжидаемаяПодпись = КодироватьBase64URL(
ВычислитьHMACSHA256(ДанныеДляПодписи, SecretПроверки));
Если ОжидаемаяПодпись = Части[2] Тогда
СтатусПодписи = "Корректна";
Иначе
СтатусПодписи = "Некорректна";
КонецЕсли;
Если вычисленная Base64URL-подпись совпадает с третьей частью JWT, статус — «Корректна». Если secret неверный или Header/Payload изменились — «Некорректна».
Создание нового JWT
В Generator системные claims задаются отдельными полями формы. Дополнительный Payload принимается как JSON-объект, но ему запрещено повторно задавать iss, sub, aud, iat и exp. Так случайный JSON не может незаметно перезаписать значения, которые пользователь указал явно.
Для Каждого ЭлементPayload Из ДополнительныйPayload Цикл
Если ЭтоСистемныйClaim(Строка(ЭлементPayload.Ключ)) Тогда
ДиагностикаГенерации =
"Дополнительный Payload содержит системное поле '"
+ Строка(ЭлементPayload.Ключ)
+ "'. Заполните его отдельным полем формы.";
Возврат;
КонецЕсли;
КонецЦикла;
Header = Новый Соответствие;
Header.Вставить("alg", "HS256");
Header.Вставить("typ", "JWT");
Payload = Новый Соответствие;
Если Не ПустаяСтрока(СокрЛП(IssuerГенерации)) Тогда
Payload.Вставить("iss", IssuerГенерации);
КонецЕсли;
Если Не ПустаяСтрока(СокрЛП(SubjectГенерации)) Тогда
Payload.Вставить("sub", SubjectГенерации);
КонецЕсли;
Если Не ПустаяСтрока(СокрЛП(AudienceГенерации)) Тогда
Payload.Вставить("aud", AudienceГенерации);
КонецЕсли;
ТекущееВремя = ТекущийUnixTimestamp();
Payload.Вставить("iat", ТекущееВремя);
Payload.Вставить("exp", ТекущееВремя + СрокЖизниМинут * 60);
iat берётся из текущего UTC-времени, а exp рассчитывается из заданного срока жизни в минутах. После этого в Payload добавляются остальные пользовательские поля.
Собираем итоговый токен
Когда Header и Payload готовы, остаётся сериализовать их в JSON, закодировать обе части в Base64URL, подписать строку header.payload и присоединить подпись.
HeaderГенерацииJSON = ЗаписатьJSONВСтроку(Header);
PayloadГенерацииJSON = ЗаписатьJSONВСтроку(Payload);
ЧастьHeader = КодироватьСтрокуBase64URL(HeaderГенерацииJSON);
ЧастьPayload = КодироватьСтрокуBase64URL(PayloadГенерацииJSON);
ДанныеДляПодписи = ЧастьHeader + "." + ЧастьPayload;
ЧастьПодписи = КодироватьBase64URL(
ВычислитьHMACSHA256(ДанныеДляПодписи, SecretГенерации));
СформированныйJWT = ДанныеДляПодписи + "." + ЧастьПодписи;
На выходе получается обычная строка JWT, которую можно сразу использовать в HTTP-запросе или передать обратно во вкладку проверки.
Как пользоваться
Проверить готовый JWT
- Вставить токен в поле JWT и нажать Разобрать.
- Посмотреть Header/Payload, claims и срок действия.
- Для HS256 указать secret и нажать Проверить подпись.
Создать JWT HS256
- Открыть вкладку Создание JWT.
- Указать secret, нужные
iss,sub,audи срок жизни. - При необходимости добавить собственные поля в дополнительный Payload JSON.
- Нажать Сформировать JWT.
Состав поставки
JWTToolkit.epf— готовая внешняя обработка с открытым кодом.
Проверено на следующих конфигурациях и релизах:
- Управление нашей фирмой, редакция 3.0, релизы 3.0.14.143
Вступайте в нашу телеграмм-группу Инфостарт