OAuth 2.0 и 1С на примере HeadHunter.ru

05 июля 2016

GitHubИсходный код на GitHub

Исходный код отчета "Анализ данных HH.ru". Здесь всегда самая последняя версия отчета.

InfostartПубликация на Infostart

Публикация на Infostart c описанием и последней стабильной версией отчета.

Welcome! Комментарий, обсуждение. Присоединяйтесь!

What are you talking about?

Многие API используют протокол авторизации OAuth 2.0 для авторизации приложений и пользователей. Например, такие интернет-ресурсы как HH.ru, VK.com или гиганты как Yandex и Google поддерживают OAuth-авторизацию, позволяющую предоставить доступ к их ресурсам сторонним приложениям без предоставления им аутентификационных данных конечных пользователей. Другими словами, OAuth-авторизация позволяет дать доступ к ресурсам сторонним приложениям, при этом не выдавая им логин и пароль пользователя. Вместо этого конечный пользователь получает токен доступа, который и является дальнейшим пропуском.

Просто и понятно о протоколе OAuth 2.0 написано в одной из статей на Хабре в блоге Mail.ru. Если Вы еще не сталкивались в работе с этим "зверем", то будет полезно ознакомиться с этим материалом прежде, чем читать дальше.

Нет, мы не будем реализовывать OAuth-авторизацию для решений на базе платформы 1С:Предприятие =). Вместо этого мы рассмотрим подробный пример использования этого протокола для получения доступа к открытому API HeadHunter  с помощью встроенных средств платформы 1С:Предприятие 8.3 (и старше). Результатом будет вот такой отчет.

Отчет для 1С "Анализ данных HH.ru"

При формировании отчет сначала обращается к API HeadHunter, затем компонует полученные данные и выводит их в табличный документ. Работа отчета базируется на СКД. Мы рассмотрим все шаги для прохождения аутентификации на сайте HH.ru и последующей авторизации для доступа к функциям API. Итак, начнем!

Подготовка

Прежде чем перейти к реализации взаимодействия с API, нам необходимо зарегистрировать приложение в личном кабинете HH.ru по адресу dev.hh.ru. Действие особого описания не требует, ничего сложного.

Регистрация приложения выполняется в три шага (см. скриншоты выше):

  1. Добавление нового приложения.
  2. Заполнение имени приложения и адреса страницы для переадресации.
    • Имя приложения - имя, под которым будет идентифицироваться Ваше приложение
    • Адрес переадресации - адрес в интернете, куда будет отправлен ответ сервера при выполнении попытки авторизации. Обязательно нужно указывать валидный адрес, иначе регистрация приложения не будет пройдена.
  3. Регистрация завершена.
    • По завершению регистрации у нас есть ClienID и ClientSecret:
      • ClientID - это идентификатор приложения
      • ClientSecret - защищенный ключ, используемый Вашим приложением для запроса авторизации к API HH.ru. Обычно ClientSecret не рекомендуется разглашать, чтобы никто другой не мог выполнять запросы к API от имени Вашего приложения.

В описываемом ниже примере для взаимодействия с API HH.ru используется мое приложение "DP_AnalysisOfTheLaborMarket", зарегистрированное мной специально для веб-приложения ALM.DevelPlatform.ru. Поэтому в примере ClientSecret фигурировать не будет. Вместо этого будет выполняться обращение к приложению ALM.DevelPlatform.ru для получения токена доступа. Именно это приложение будет формировать запрос к API HH.ru с использованием ClientSecret.

Анализ данных HH.ru

Подробнее об использовании API HeadHunter, в т.ч. об авторизации с помощью OAuth 2.0, можно прочитать здесь. Теперь перейдем непосредственно к взаимодействию с API со стороны 1С:Предприятия.

Схема реализации

Общая схема аутентификации и авторизации по протоколу OAuth в нашем случае будет примерно следующей:

  1. С клиентской стороны открывается веб-страница аутентификации на сайте HH.ru, где необходимо ввести логин и пароль.
  2. Если логин и пароль были введены корректно, то происходит переадресация на страницу, указанную на этапе регистрации приложения.
    • При переадресации в адресную строку добавляется авторизационный код, который будет использоваться для получения токена доступа.
    • Нам необходимо перехватить факт переадресации и получить из адресной строки код авторизации.
  3. С помощью кода авторизации делаем HTTP-запрос к HH.ru для получения токена доступа. Если код авторизации корректный, то получаем из результата запроса токен доступа и сохраняем его.
  4. При выполнении последующих запросов к API добавляем токен в качестве заголовка HTTP-запросов. Желательно при каждом обращении к API проверять действительность токена доступа и если токен перестал быть действительным запросить повторную авторизацию.

В пользовательском режиме это будет выглядеть следующим образом:

Авторизация для API HH.ru из 1С

Теперь рассмотрим нюансы реализации с технической точки зрения.

Пишем код на 1С

Начнем со стадии аутентификации, где пользователю открывается веб-страница входя HH.ru с предложением ввести логин и пароль. Сама форма достаточно простая, на ней находится поле HTML-документа через которое и выполняется перехват переадресации и получение кода авторизации (НЕ токена доступа).

Форма аутентификации для HH.ru

В событии "ПриСозданииНаСервере" прописан код для открытия страницы аутентификации.

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	
	client_id = "VRJVLR41NI354P2DPQ1ONE8DL93BOI02N51EC78VTTLB5JLDGMSAE74SIPB3LS3A"; 
	СтраницаАвторизации = "https://hh.ru/oauth/authorize?response_type=code&client_id="+client_id+"&state=letsStartAgain";
	
КонецПроцедуры

Обратите внимание, что для открываемой страницы передается параметр "client_id", в котором содержится идентификатор клиентского приложения, который был получен на этапе регистрации. Пользователь вводит логин и пароль и, если данные корректные, происходит переадресация на страницу редиректа приложения с передачей кода авторизации. В событии "ДокументСформирован" поля HTML-документа мы отслеживаем появление этого кода в адресной строке. Как только код найден выполняется его передача в форму владельца.

&НаКлиенте
Процедура ПриЗакрытии()
	
	Если КодАвторизацииПолучен Тогда
		СтруктураРезультат = Новый Структура;
		СтруктураРезультат.Вставить("КодАвторизации", КодАвторизации);
		ОповеститьОВыборе(СтруктураРезультат);
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура СтраницаАвторизацииДокументСформирован(Элемент)
	
	Если ПеременнаяСодержитСвойство(Элемент, "Документ") Тогда
		ТекущийURLСтраницы = ПолучитьТекущийURLСтраницы(Элемент.Документ);
		Если ЗначениеЗаполнено(ТекущийURLСтраницы) Тогда		
			СтрокаНачалаКодаАвторизации = "&code=";
			ИндексНачалаКодаАвторизации = СтрНайти(ТекущийURLСтраницы, СтрокаНачалаКодаАвторизации);
			Если ИндексНачалаКодаАвторизации > 0 Тогда
				НачалоКодаАвторизации = ИндексНачалаКодаАвторизации + СтрДлина(СтрокаНачалаКодаАвторизации);	
				КодАвторизации = Сред(ТекущийURLСтраницы, НачалоКодаАвторизации, СтрДлина(ТекущийURLСтраницы) - НачалоКодаАвторизации + 1);
				КодАвторизацииПолучен = Истина;
				СтраницаАвторизации = Неопределено;
				Закрыть();
			КонецЕсли;
		КонецЕсли;
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Функция ПолучитьТекущийURLСтраницы(ДокументБраузера)
	
	URLСтраницы = Неопределено;
	
	// Получение URL для клиента из под Windows
	Если ПеременнаяСодержитСвойство(ДокументБраузера, "URLUnencoded") Тогда
		URLСтраницы = ДокументБраузера.URLUnencoded;
	// Получение URL для клиента из под Linux
	ИначеЕсли ПеременнаяСодержитСвойство(ДокументБраузера, "URL") Тогда
		URLСтраницы = ДокументБраузера.URL;
	ИначеЕсли ПеременнаяСодержитСвойство(ДокументБраузера, "documentURI") Тогда
		URLСтраницы = ДокументБраузера.documentURI;
	КонецЕсли;
	
	Возврат URLСтраницы;
	
КонецФункции

Обработка всех действий выполняется на клиенте, поэтому возник вопрос получения текущего URL-адреса из поля HTML-документа для систем Windows и Linux. Если запуск клиентского приложения выполняется в среде Windows, то текущий URL находится в свойстве "URLUnencoded". Для клиентов, запущенных в семействе операционных систем Linux, текущий URL находится либо в свойстве "URL", либо в свойстве "documentURI". В функции "ПолучитьТекущийURLСтраницы" как раз эта ситуация и обрабатывается.

После того, как код авторизации был отправлен назад в основную форму отчета, выполняется функция получения токена доступа.

&НаКлиенте
Процедура ОбработкаВыбора(ВыбранноеЗначение, ИсточникВыбора)
	
	Если ИсточникВыбора.ИмяФормы = ПутьМетаданныхОтчета + ".Форма.ЗапросКодаАвторизации" Тогда
		Отчет.ТокенДоступа = ПолучитьТокенДоступа(ВыбранноеЗначение.КодАвторизации);
		Если ЗначениеЗаполнено(Отчет.ТокенДоступа) Тогда
			ПроверитьАвторизацию();
			ПоказатьПредупреждение(, "Здравствуйте, "+Отчет.АвторизированныйПользователь+"!
				|Приятной работы!");
		КонецЕсли;
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Функция ПолучитьТокенДоступа(КодАвторизации)

	ТокенДоступа = Неопределено;
	
	Попытка
		ЗаголовкиЗапроса = Новый Соответствие;	
		ЗаголовкиЗапроса.Вставить("Content-Type", "application/json;charset=utf-8");
		ЗапросТокена = Новый HTTPЗапрос("/HeadHunter/GetAuthToken?code="+КодАвторизации, ЗаголовкиЗапроса);	
		
		HTTPСоединение = Новый HTTPСоединение("alm.develplatform.ru"); 
	 	HTTPОтвет = HTTPСоединение.Получить(ЗапросТокена);
		
		ОтветСтрокаJSON = HTTPОтвет.ПолучитьТелоКакСтроку();
		
		ЧтениеJSON = Новый ЧтениеJSON;
		ЧтениеJSON.УстановитьСтроку(ОтветСтрокаJSON);
		Пока ЧтениеJSON.Прочитать() Цикл
			Если ЧтениеJSON.ТипТекущегоЗначения = ТипЗначенияJSON.ИмяСвойства
				И ЧтениеJSON.ТекущееЗначение = "access_token" Тогда
				Если ЧтениеJSON.Прочитать() Тогда
					ТокенДоступа = ЧтениеJSON.ТекущееЗначение;				
				КонецЕсли;
				Прервать;
			КонецЕсли;
		КонецЦикла;
	Исключение
		ПодробноеОписаниеОшибки = ИнформацияОбОшибке();
		Сообщить("Не удалось выполнить авторизацию: " + ПодробноеОписаниеОшибки.Причина.Описание);
		ТокенДоступа = Неопределено;	
	КонецПопытки;
	
	Возврат ТокенДоступа;
	 	
КонецФункции
Получение токена доступа также выполняется с клиентской стороны. В примере выполняется запрос к моему сервису ALM.DevelPlatform.ru с передачей кода авторизации. На сервере код авторизации подставляется в запрос к HH.ru вместе с защищенным ключом. В результате запрос возвращает на клиент 1С:Предприятия токен доступа, который сохраняется в настройках отчета и используется для всех дальнейших взаимодействий с API HH.ru.
Чтобы открыть "черный ящик" и показать как именно делает сервис ALM.DevelPlatform запрос к HH.ru для получения токена, приведу листинг кода метода "GetAuthToken" (C#), к которому и выполнялся запрос выше:
[HttpGet]
public JsonResult GetAuthToken(string code) {
	// Информация для выполнения запроса получения токена доступа
	string clientId = "VRJVLR41NI354P2DPQ1ONE8DL93BOI02N51EC78VTTLB5JLDGMSAE74SIPB3LS3A";
	string clientSecret = "<<!!! ЗДЕСЬ ВАШ CLIENT_SECRET !!!>>";
	string grantType = "authorization_code";
	string authService = "https://hh.ru/oauth/token";

	// Формируем HTTP-запрос с типом POST
	HttpWebRequest request = (HttpWebRequest) WebRequest.Create(authService);
	request.Method = "POST";
	request.Credentials = CredentialCache.DefaultCredentials;

	// Подготавливаем данные для отправки
	Dictionary < string, object > postedData = new Dictionary < string, object > ();
	postedData.Add("grant_type", grantType);
	postedData.Add("client_id", clientId);
	postedData.Add("client_secret", clientSecret);
	postedData.Add("code", code);
	StringBuilder postedDataAsString = new StringBuilder();
	foreach(var elem in postedData) {
		if (postedDataAsString.Length != 0)
			postedDataAsString.Append("&");
	
		postedDataAsString.Append(elem.Key);
		postedDataAsString.Append("=");
		postedDataAsString.Append(elem.Value);
	}
	UTF8Encoding encoding = new UTF8Encoding();
	var bytes = encoding.GetBytes(postedDataAsString.ToString());

	request.ContentType = "application/x-www-form-urlencoded";
	request.ContentLength = bytes.Length;

	using(var newStream = request.GetRequestStream()) {
		newStream.Write(bytes, 0, bytes.Length);
		newStream.Close();
	}

	// Обрабатываем результат и возвращаем в качестве JSON
	Dictionary < string, object > queryResult = new Dictionary < string, object > ();

	dynamic data = null;
	HttpWebResponse response = (HttpWebResponse) request.GetResponse();
	using(StreamReader streamReader = new StreamReader(response.GetResponseStream())) {
		//ответ от сервера
		string responseAsJSONString = streamReader.ReadToEnd();
		data = System.Web.Helpers.Json.Decode(responseAsJSONString);
		foreach(var item in data.GetDynamicMemberNames()) {
			queryResult.Add(item, data[item]);
		}
	}

	return Json(queryResult, JsonRequestBehavior.AllowGet);
}
Примечание: если вы создаете приложение только для себя, то Вам не обязательно создавать какой-либо сторонний сервис для получения токена доступа. Достаточно будет выполнить этот же HTTP-запрос к HH.ru для получения токена прямо из 1С, но тогда защищенный ключ (client secret) нужно будет также хранить в коде обработки, отчета или конфигурации, что не является безопасным, т.к. закрытые 1Сные модули никогда никого не остановят =).

Пока что все

Реализовать авторизацию через протокол OAuth 2.0 из 1С:Предприятия не такая уже и сложная задача как может показаться на первый взгляд. Все что нужно - это опыт работы с полем HTML-документа и умение создавать HTTP-запросы разной сложности. Но даже это не так важно, как прочитать официальную документацию, которая даст ответы на все Ваши вопросы.

Подобным образом можно реализовать авторизацию для большинства сервисов Google, Yandex, VK.com и многих других. Конечно, будут нюансы, которые следует учитывать, но о них нужно узнавать в соответствующих мануалах к API.


comments powered by Disqus