Интеграция 1С с облачными хранилищами — частая задача при организации резервного копирования, обмена данными с внешними сайтами или выгрузки отчетов. Одним из наиболее надежных решений является Google Cloud Storage (GCS). Однако разработчики часто сталкиваются с трудностями при настройке авторизации по файлу закрытого ключа keyfile.json и правильном формировании HTTP-запросов к REST API Google без использования внешних компонент и библиотек.
В этой статье мы подробно разберем, как реализовать этот процесс штатными средствами 1С. Мы научимся извлекать данные авторизации, получать временный токен доступа (Bearer token), правильно формировать тело и параметры запроса для загрузки файлов, а также разберем типичные ошибки безопасности и управления правами доступа.
Для взаимодействия с сервисами Google используется сервисный аккаунт. При его создании скачивается файл ключа в формате JSON (обычно с именем вроде keyfile.json). Этот файл содержит конфиденциальные данные, необходимые для генерации токена доступа.
Давайте проанализируем структуру этого файла. Внутри находятся следующие ключевые поля:
client_email — уникальный идентификатор (электронная почта) сервисного аккаунта;private_key — закрытый ключ в формате PEM, используемый для криптографической подписи запросов.Для получения токена доступа (Access Token) напрямую из 1С нам необходимо сформировать так называемый JWT-токен (JSON Web Token), подписать его закрытым ключом по алгоритму RS256 и отправить на сервер авторизации Google. Разберем этот процесс по шагам.
Сначала подготовим структуру JWT-токена. Он состоит из заголовка (Header) и полезной нагрузки (Payload), закодированных в формат Base64URL:
// Пример формирования структуры JWT для обмена на токен доступа
ЗаголовокJWT = "{"alg":"RS256","typ":"JWT"}";
ТекущаяДатаUnix = Формат((ТекущаяДата() - Дата(1970, 1, 1, 0, 0, 0)), "ЧГ=0");
ВремяИстеченияUnix = Формат((ТекущаяДата() + 3600 - Дата(1970, 1, 1, 0, 0, 0)), "ЧГ=0"); // Токен на 1 час
ПолезнаяНагрузка = "{" +
""iss":"" + СодержимоеКлюча.client_email + ""," +
""scope":"https://www.googleapis.com/auth/devstorage.read_write"," +
""aud":"https://oauth2.googleapis.com/token"," +
""exp"":" + ВремяИстеченияUnix + "," +
""iat"":" + ТекущаяДатаUnix +
"}";
После подготовки данных их необходимо подписать приватным ключом. В 1С для этого можно использовать встроенный объект МенеджерКриптографии или внешние консольные утилиты (например, OpenSSL), установленные на сервере. Подписанная строка отправляется POST-запросом на адрес https://oauth2.googleapis.com/token с параметром grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. В ответ Google пришлет JSON-структуру, содержащую временный access_token, который мы и будем использовать в качестве Bearer-токена для всех последующих операций.
Основная сложность, с которой сталкиваются разработчики при работе с GCS API — это правильная передача параметров запроса. Попытка указать имя файла и тип загрузки в заголовках HTTP-запроса приведет к ошибке доступа или неверному имени объекта в облаке.
Выясним причину: согласно спецификации Google Cloud Storage REST API, при простой загрузке (типа media) имя результирующего объекта и тип загрузки должны передаваться строго в строке URL-запроса в качестве GET-параметров, а не в HTTP-заголовках.
Проанализируем правильный шаблон адреса ресурса:
/upload/storage/v1/b/[ИМЯ_БАКЕТА]/o?uploadType=media&name=[ИМЯ_ФАЙЛА_В_ОБЛАКЕ]
Ниже представлен готовый к использованию рабочий пример кода на языке 1С для выгрузки файла в бакет:
// Функция загрузки локального файла в Google Cloud Storage
Функция ЗагрузитьФайлВОблако(ТокенДоступа, ИмяБакета, ПутьКЛокальномуФайлу, ИмяФайлаВОблаке) Экспорт
OpenSSL = Новый ЗащищенноеСоединениеOpenSSL();
Хост = "storage.googleapis.com";
// Получаем информацию о локальном файле для определения его размера
ФайлОтправки = Новый Файл(ПутьКЛокальномуФайлу);
РазмерФайла = XMLСтрока(ФайлОтправки.Размер());
// КРИТИЧЕСКИ ВАЖНО: Имя файла в облаке кодируем для URL, чтобы избежать проблем со спецсимволами
КодированноеИмяФайла = КодироватьСтроку(ИмяФайлаВОблаке, СпособКодированияСтроки.URL);
// Формируем правильный ресурсный путь с GET-параметрами
РесурсЗапроса = "/upload/storage/v1/b/" + ИмяБакета + "/o?uploadType=media&name=" + КодированноеИмяФайла;
// Настраиваем заголовки HTTP-запроса
Заголовки = Новый Соответствие();
Заголовки.Вставить("Authorization", "Bearer " + ТокенДоступа);
Заголовки.Вставить("Content-Length", РазмерФайла);
// Указываем корректный MIME-тип для передаваемого контента
Если НРег(ФайлОтправки.Расширение) = ".csv" Тогда
Заголовки.Вставить("Content-Type", "text/csv; charset=UTF-8");
Иначе
Заголовки.Вставить("Content-Type", "application/octet-stream");
КонецЕсли;
// Создаем соединение и запрос
HTTPСоединение = Новый HTTPСоединение(Хост, , , , , , OpenSSL);
HTTPЗапрос = Новый HTTPЗапрос(РесурсЗапроса, Заголовки);
// Устанавливаем двоичные данные файла непосредственно в тело запроса
HTTPЗапрос.УстановитьИмяФайлаТела(ПутьКЛокальномуФайлу);
Попытка
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Если HTTPОтвет.КодСостояния = 200 Тогда
ЗаписьЖурналаРегистрации("GCS.Загрузка", УровеньЖурналаРегистрации.Информация, , ,
"Файл " + ИмяФайлаВОблаке + " успешно загружен в бакет " + ИмяБакета);
Возврат Истина;
ИначеЕсли HTTPОтвет.КодСостояния = 401 Тогда
ВызватьИсключение "Ошибка авторизации: Истек или не принят OAuth-токен.";
Иначе
ВызватьИсключение "Ошибка загрузки! Код состояния: " + HTTPОтвет.КодСостояния +
". Ответ сервера: " + HTTPОтвет.ПолучитьТелоКакСтроку();
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("GCS.Ошибка", УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Ложь;
КонецПопытки;
КонецФункции
Если при попытке выполнить запрос 1С возвращает ошибку 403 Forbidden с диагностическим сообщением "Insufficient Permission" (недостаточно прав), необходимо проверить настройки безопасности на стороне Google Cloud Platform Console.
Для исправления этой проблемы выполним следующие действия:
client_email файла keyfile.json).Часто бизнес-процесс требует, чтобы загруженный файл был доступен для скачивания по прямой ссылке без авторизации (например, для интеграции с маркетплейсами или CRM). Попытка сделать это путем вызова метода setIamPolicy на весь бакет (как показано в сообщении №42) является крайне опасной операцией.
Внимание! Вызов setIamPolicy через метод PUT полностью перезаписывает всю политику прав доступа бакета. Если отправить туда JSON-структуру, содержащую только роль чтения для всех пользователей (allUsers), вы удалите права доступа для всех остальных учетных записей, включая администраторов проекта и саму систему 1С!
Чтобы безопасно предоставить публичный доступ только к конкретному загруженному файлу, рассмотрим использование механизмов **Object ACL** (списков управления доступом на уровне объекта). Разберем по шагам, как это реализовать:
// Процедура делает публичным конкретный объект в бакете GCS
ПроцедураСделатьОбъектПубличным(ТокенДоступа, ИмяБакета, ИмяФайлаВОблаке)
OpenSSL = Новый ЗащищенноеСоединениеOpenSSL();
Хост = "storage.googleapis.com";
// Кодируем имя объекта для формирования валидного URI
КодированноеИмяФайла = КодироватьСтроку(ИмяФайлаВОблаке, СпособКодированияСтроки.URL);
РесурсЗапроса = "/storage/v1/b/" + ИмяБакета + "/o/" + КодированноеИмяФайла + "/acl";
// Тело запроса, определяющее публичный доступ на чтение (READER) для всех пользователей (allUsers)
ТелоJSON = "{
| ""entity"": ""allUsers"",
| ""role"": ""READER""
|}";
Заголовки = Новый Соответствие();
Заголовки.Вставить("Authorization", "Bearer " + ТокенДоступа);
Заголовки.Вставить("Content-Type", "application/json; charset=UTF-8");
HTTPСоединение = Новый HTTPСоединение(Хост, , , , , , OpenSSL);
HTTPЗапрос = Новый HTTPЗапрос(РесурсЗапроса, Заголовки);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Попытка
// Для добавления правила в ACL используется метод POST
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Если HTTPОтвет.КодСостояния = 200 Тогда
Сообщить("Файл успешно опубликован. Ссылка для скачивания: ");
Сообщить("https://storage.googleapis.com/" + ИмяБакета + "/" + ИмяФайлаВОблаке);
Иначе
ВызватьИсключение HTTPОтвет.ПолучитьТелоКакСтроку();
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("GCS.ОшибкаПубликации", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
При реализации обмена с внешними API важно учитывать особенности выполнения кода на платформе 1С:Предприятие:
HTTPСоединение.ОтправитьДляОбработки() или ВызватьHTTPМетод()), поскольку они заблокируют интерфейс пользователя и вызовут предупреждение платформы об устаревшем коде. Вместо этого применяйте конструкции с асинхронными вызовами, например, ОтправитьДляОбработкиАсинх().Используя предложенные шаблоны кода и следуя правилам конфигурирования прав доступа в GCP, мы можем надежно связать информационную базу 1С с облачным хранилищем Google Cloud Storage, обеспечив высокую скорость передачи и безопасность обмена данными.