Прежде чем начать
Как тестовый пример будем рассматривать описание API банка «Приватбанк». Детально описание можно найти по ссылке. Данный пример хорош тем, что затрагивает взаимодействие с пользователем, мобильными номерами (OTP-авторизация, правда не во всех случаях), а так же имеется разграничения прав как на уровне приложения так и на уровне доступной роли клиента. Даже беглое описание, наверное, вызывает определенные сложности в оценке времени на разработку, а так же вопросы по поводу организации кода.
Анализируем шаги авторизации
Весь процесс состоит из четырех последовательных шагов:
- Получение ID сессии — состоит в том, что бы по
R03;ID
иR03;secret
приложения получить R03;R03;token
, который будет передаваться с каждым запросом к Web-сервису банка, в данном случае так же получим права доступа; - Авторизация с помощью пары логин/пароль пользователя — основная цель это повысить права доступа
R03;token
полученного на предыдущем шаге; - Прохождение OTP-авторизации — если используется двух-факторная авторизация необходимо отправить запрос на получения OTP-пароля, который придет на мобильное устройство;
- Проверка OTP-авторизации — отправляем подтверждение повышения прав с помощью OTP-пароля.
Определим объекты которые будут необходимы:
- Id — идентификатор сессии;
- ExpiresIn — дата в формате Unix Timestamp, когда истечет сессия;
- ApplicationId — идентификатор приложения;
- ApplicationSecret — секрет приложения;
- Login — логин пользователя;
- Password — пароль пользователя;
- OtpDev — устройство для получения OTP-пароля;
- Otp — OTP-пароль полученный на устройство;
- Роли — таблица ролей доступных сессии.
Срок истечения сессии достаточно мал, получается, что нет смысла выполнять предварительную авторизацию и хранить данные сессии. Логично сохранять только текущую сессию и при каждом запросе к Web-сервису банка проверять валидность сессии и необходимые права доступа к команде сервиса. При надобности обновлять сессию и повышать права к определенному уровню. Теперь можно переходить к псевдокоду.
ШАГ 0. Выбор команды Web-сервиса банка
После предварительного анализа уже понятно, что необходимо для каждого запроса выполнять проверку сессии и если есть потребность проходить авторизацию. Сделаем это с помощью псевдокода для выписок банка:
&НаКлиенте Процедура ВыпискиПоСчетуНаКлиенте() ВыпискиИзБанка.Очистить(); ОписаниеОповещения = Новый ОписаниеОповещения( "ВыполнитьОбновлениеВыписокПоСчету", ЭтотОбъект, Новый Структура("Роль", "ROLE_P24_BUSINESS")); АвторизироватьКлиентБанк(ОписаниеОповещения); КонецПроцедуры // ВыпискиПоСчетуНаКлиенте() &НаКлиенте Процедура ВыполнитьОбновлениеВыписокПоСчету(Результат, ДополнительныеПараметры = Неопределено) Экспорт Если Результат = Истина Тогда ОбновитьДанныеПоСчетуНаСервере(); КонецЕсли; КонецПроцедуры // ВыполнитьОбновлениеВыписокПоСчету()
Происходит очистка объекта в который будет выполнятся загрузка выписок банка, далее создается описание оповещения, которое будет выполнено при актуальной сессии и доступной роли для данной операции (Роль передается как структура в дополнительных параметрах).
ШАГ 1. Получаем ID сессии
&НаКлиенте Процедура АвторизироватьКлиентБанк(ОписаниеЭтапаПолученияДанных) // Очищаем чтобы проверить доступные роли для токена и сравнить с необходимой // ролью для выполнения запроса указаного в описании оповещения. Роли.Очистить(); // Проходим авторизацию приложения ПриложениеАвторизация = АвторизироватьПриложение(); ОписаниеСледующиегоЭтапаАвторизации = Новый ОписаниеОповещения( "ВыполнитьПослеАвторизацииПриложения", ЭтотОбъект, ПриложениеАвторизация); ОбработатьРезультатАвторизации(ПриложениеАвторизация, ОписаниеЭтапаПолученияДанных, ОписаниеСледующиегоЭтапаАвторизации); КонецПроцедуры // АвторизироватьКлиентБанк() &НаСервере Функция АвторизироватьПриложение() // Заполняем ApplicationId, ApplicationSecret, Login, Password из БД или данных формы. ЗаполнитьДанныеАвторизации(); // Id сессии может быть заполнен, тогда при вызове необходимо проверить его валидность // https://link.privatbank.ua/console/wiki/client_auth Валидация SessionID. ОбработкаОбъект = РеквизитФормыВЗначение("Объект"); РезультатАвторизации = ОбработкаОбъект.АвторизироватьПриложение( ApplicationId, ApplicationSecret, Id); ЗначениеВРеквизитФормы(ОбработкаОбъект, "Объект"); Возврат РезультатАвторизации; КонецФункции // АвторизироватьПриложение()
После авторизации или валидации приложения, необходимо проверить результат выполнения. Если валидация сессии прошла успешно и роль для вызова операции Web-сервера банка доступна, тогда выполняем запрос на получения полезных данных, иначе переходим на следующий этап авторизации.
&НаКлиенте Процедура ОбработатьРезультатАвторизации(Результат, ОписаниеЭтапаПолученияДанных, ОписаниеСледующиегоЭтапаАвторизаци = Неопределено) Перем МассивРолей; Если ТипЗнч(Результат) = Тип("ФиксированнаяСтруктура") Тогда Результат.Свойство("Id", Id); Результат.Свойство("ExpiresIn", ExpiresIn); Если Результат.Свойство("Roles", МассивРолей) Тогда Роли.Очистить(); Для Каждого ЭлементМассива Из МассивРолей Цикл Роли.Добавить().Роль = ЭлементМассива; КонецЦикла; КонецЕсли; КонецЕсли; АвторизацияПройдена = Ложь; Если ТипЗнч(ОписаниеЭтапаПолученияДанных) = Тип("ОписаниеОповещения") Тогда ПараметрыОтбора = ОписаниеЭтапаПолученияДанных.ДополнительныеПараметры; Если ТипЗнч(ПараметрыОтбора) = Тип("Структура") Тогда Если Роли.НайтиСтроки(ПараметрыОтбора).Количество() > 0 Тогда; АвторизацияПройдена = Истина; ВыполнитьОбработкуОповещения(ОписаниеЭтапаПолученияДанных, Истина); КонецЕсли; КонецЕсли; КонецЕсли; Если АвторизацияПройдена = Ложь Тогда Если ТипЗнч(ОписаниеСледующиегоЭтапаАвторизаци) = Тип("ОписаниеОповещения") Тогда ВыполнитьОбработкуОповещения(ОписаниеСледующиегоЭтапаАвторизаци, ОписаниеЭтапаПолученияДанных); КонецЕсли; КонецЕсли; КонецПроцедуры // ОбработатьРезультатАвторизации()
ШАГ 2. Авторизация с помощью пары логин/пароль
// Процедура выполняется после успешной авторизации приложения и если доступные // роли не удовлетворяют требованиям выполняемого запроса к клиент-банку. // // Параметры: // ОписаниеЭтапаПолученияДанных - ОписаниеОповещения - оповещение которое будет выполнено если // авторизация будет успешной. // ПриложениеАвторизация - Произвольный - результат предыдущего этапа авторизации. // &НаКлиенте Процедура ВыполнитьПослеАвторизацииПриложения(ОписаниеЭтапаПолученияДанных, ПриложениеАвторизация = Неопределено) Экспорт // Проходим авторизацию клиента КлиентАвторизация = АвторизироватьКлиента(); ОписаниеСледующиегоЭтапаАвторизации = Новый ОписаниеОповещения( "ВыполнитьПослеАвторизацииКлиента", ЭтотОбъект, КлиентАвторизация); ОбработатьРезультатАвторизации(КлиентАвторизация, ОписаниеЭтапаПолученияДанных, ОписаниеСледующиегоЭтапаАвторизации); КонецПроцедуры // ВыполнитьПослеАвторизацииПриложения() &НаСервере Функция АвторизироватьКлиента() ОбработкаОбъект = РеквизитФормыВЗначение("Объект"); РезультатАвторизации = ОбработкаОбъект.АвторизироватьКлиента( Login, Password, Id); ЗначениеВРеквизитФормы(ОбработкаОбъект, "Объект"); Возврат РезультатАвторизации; КонецФункции // АвторизироватьКлиента()
После авторизации пользователя, необходимо проверить результат выполнения. Если валидация сессии прошла успешно и роль для вызова операции Web-сервера банка доступна, тогда выполняем запрос на получения полезных данных, иначе переходим на следующий этап авторизации.
ШАГ 3. Прохождение OTP-авторизации
// Процедура выполняется после успешной авторизации клиента и если необходимо // пройти OTP-авторизацию клиент-банка. // // Параметры: // ОписаниеЭтапаПолученияДанных - ОписаниеОповещения - оповещение которое будет выполнено если // авторизация будет успешной. // КлиентАвторизация - Произвольный - результат предыдущего этапа авторизации. // &НаКлиенте Процедура ВыполнитьПослеАвторизацииКлиента(ОписаниеЭтапаПолученияДанных, КлиентАвторизация = Неопределено) Экспорт Если ТипЗнч(КлиентАвторизация) = Тип("ФиксированнаяСтруктура") Тогда Если ТипЗнч(КлиентАвторизация.Message) = Тип("ФиксированныйМассив") Тогда ПараметрыФормы = Новый Структура("НомераТелефонов", КлиентАвторизация.Message); ОткрытьФорму("ВнешняяОбработка.Приват24.Форма.ФормаВыбораНомераТелефона", ПараметрыФормы, ЭтотОбъект, , , , Новый ОписаниеОповещения( "ВыполнитьПослеЗакрытияФормыВыбораТелефона", ЭтотОбъект, ОписаниеЭтапаПолученияДанных), РежимОткрытияОкнаФормы.БлокироватьОкноВладельца); Иначе ОткрытьФорму("ВнешняяОбработка.Приват24.Форма.ФормаOTP", , ЭтотОбъект, , , , Новый ОписаниеОповещения( "ВыполнитьПослеЗакрытияФормыOTP", ЭтотОбъект, ОписаниеЭтапаПолученияДанных), РежимОткрытияОкнаФормы.БлокироватьОкноВладельца); КонецЕсли; КонецЕсли; КонецПроцедуры // ВыполнитьПослеАвторизацииКлиента() &НаКлиенте Процедура ВыполнитьПослеЗакрытияФормыВыбораТелефона(РезультатЗакрытия, ОписаниеЭтапаПолученияДанных = Неопределено) Экспорт Если ЗначениеЗаполнено(РезультатЗакрытия) Тогда OtpDev = РезультатЗакрытия; КлиентАвторизация = АвторизироватьКлиентаОтправитьOTP(); ОписаниеСледующиегоЭтапаАвторизации = Новый ОписаниеОповещения( "ВыполнитьПослеАвторизацииКлиента", ЭтотОбъект, КлиентАвторизация); ОбработатьРезультатАвторизации(КлиентАвторизация, ОписаниеЭтапаПолученияДанных, ОписаниеСледующиегоЭтапаАвторизации); КонецЕсли; КонецПроцедуры // ВыполнитьПослеЗакрытияФормыВыбораТелефона()
ШАГ 4. Проверка OTP-авторизации
На последнем этапе уже нет необходимости создавать описание оповещения для следующего этапе авторизации, этот уже последний.
&НаКлиенте Процедура ВыполнитьПослеЗакрытияФормыOTP(РезультатЗакрытия, ОписаниеЭтапаПолученияДанных = Неопределено) Экспорт Если ЗначениеЗаполнено(РезультатЗакрытия) Тогда Otp = РезультатЗакрытия; КлиентАвторизация = АвторизироватьКлиентаПроверитьOTP(); ОбработатьРезультатАвторизации(КлиентАвторизация, ОписаниеЭтапаПолученияДанных); КонецЕсли; КонецПроцедуры // ВыполнитьПослеЗакрытияФормыOTP() &НаСервере Функция АвторизироватьКлиентаПроверитьOTP() ОбработкаОбъект = РеквизитФормыВЗначение("Объект"); РезультатАвторизации = ОбработкаОбъект.АвторизироватьКлиентаПроверитьOTP( Otp, Id); ЗначениеВРеквизитФормы(ОбработкаОбъект, "Объект"); Возврат РезультатАвторизации; КонецФункции // АвторизироватьКлиентаПроверитьOTP()
Если все прошло успешно, будут получены выписки из банка. В случае проблем, ЗначениеВРеквизитФормы использовалось для того что бы формировать вполне симпатичные логи:
10.09.2024 23:36:59:
REQUEST URL
Production Base URL: link.privatbank.ua
Operation: POST /api/auth/createSessionREQUEST BODY
{
"clientId": "*******",
"clientSecret": "*******"
}10.09.2024 23:36:59:
RESPONSE
{"id":"*******","clientId":"*******","expiresIn":1505079419,"roles":["ROLE_CLIENT"]}10.09.2024 23:36:59: Выполнен запрос авторизации приложения (279 мс).
10.09.2024 23:36:59: OK: Приложение успешно авторизировано.
10.09.2024 23:36:59:REQUEST URL
Production Base URL: link.privatbank.ua
Operation: POST /api/p24BusinessAuth/createSessionREQUEST BODY
{
"login": "*******",
"password": "*******",
"sessionId": "*******"
}10.09.2024 23:37:00:
RESPONSE
{"id":"*******","clientId":"*******","expiresIn":1505079419,"message":"Authentication successfull","roles":["ROLE_P24_BUSINESS","ROLE_CLIENT"]}10.09.2024 23:37:00: Выполнен запрос авторизации клиента (1 129 мс).
10.09.2024 23:37:00: Authentication successfull
10.09.2024 23:37:00:REQUEST URL
Production Base URL: link.privatbank.ua
Operation: GET /api/p24b/statements?acc=26006054710862&stdate=10.09.2024&endate=10.09.202410.09.2024 23:37:01:
RESPONSE
{}10.09.2024 23:37:01: Выполнен запрос для получения выписок из банка (552 мс).
10.09.2024 23:37:01: OK: Выписки из банка за указанный период отсутствуют.
Вместо послесловия
Надеюсь, вы заметили шаблон, каждый этап авторизации имеет всего две процедуры или функции: первая процедура предназначена для создания описания оповещения следующего шага авторизации и вызова процедуры/функции/формы текущего шага обработчика, а в самом конце выполняется проверка успешности и переход к получению полезных данных или на следующий шаг авторизации.
Статья в личном блоге клац
Сейчас как раз занимаюсь автоматической загрузкой выписок из 3х разных банков в старую-древнюю УПП. Посмотрел код реализации DirectBank в Библиотеке электронных документов и решил пойти другим путем. Просто в конфигурации БЭД написал обработку, которая с заданной периодичностью запрашивает документы из банков по уже готовым алгоритмам, а потом просто выгружает полученные файлы в УПП. Задача решилась малой кровью. А так да, смотреть без слез на то, что написано в БЭД не возможно.
Автор, надо бы выложить обработку. Иначе единственное, что понятно из этой статьи, так это то, что Вы очень умный, а я нет. Если это было целью, то Вы добились своего.
(2)Это шаблон архитектуры, весь код с примерами прямо в статье, какая ещё обработка?
В коде есть ссылки на объекты, например, Роли. В коде есть ссылки на формы обработки. Без обработки это не код, а сочинение на вольную тему. Представьте, что Вы читатель статьи, и Вам нужно что-то сделать. Как ? Впрочем, хозяин-барин.
(4) Роли это простая структура которая согласуется с описанием АPI (https://link.privatbank.ua/console/wiki/p24business_auth) , это не роли конфигурации.
«Объект» это объект обработки, основной реквизит формы.
Каждый переход в модуль обработки, это обычный HTTP запрос к сервису банка и преобразование ответа в структурумассив, цель статьи не дать готовое решение, а подумать и развить аналитические навыки.
Вот, Петр Цап, тоже написал статью над которой необходимо было подумать, и там ни строчки кода, но есть классные идеи.
(5) Спасибо на добром слове 🙂 Я ваши статьи тоже сейчас активно осмысливаю на предмет практического применения, вот эта была прямо вообще в тему.