Авторизация в системе ELMA при помощи внешних провайдеров аутентификации

Внимание!
Данная статья актуальна только для версий системы 3.13.27 и выше.

Система ELMA поддерживает возможность авторизации пользователей через внешние системы аутентификации, например, Вконтакте, Google, Facebook и т.д.

Важная информация
Аутентификация работает на основе протокола https://oauth.net/2/. Перед настройкой возможности авторизации в ELMA необходимо настроить внешний провайдер, с которым осуществляется интеграция. Подробнее о настройке внешних провайдеров см. справочные материалы по конкретной внешней системе. В статье рассмотрен пример авторизации при помощи социальной сети «Вконтакте», справочная информация по данной системе - https://vk.com/dev/access_token.

Пример отображения данных

Рис. 1. Пример окна авторизации в системе ELMA с реализованной возможностью входа через внешние системы

Методы расширения

Для реализации данной возможности необходимо добавить реализацию точки расширения IOAuthProvider. Данная точка расширения имеет следующие методы:

public interface IOAuthProvider
    {
        /// <summary>
        /// Название провайдера
        /// </summary>
        string ProviderName { get; } //Отображаемое имя провайдера, будет использовано в профиле пользователя на кнопках привязки и отвязки аккаунта

        /// <summary>
        /// Путь до иконки 16x16
        /// </summary>
        string Icon16 { get; } //Путь до иконки с названием файла. Также используется на странице профиля пользователя

        /// <summary>
        /// Путь до иконки 24x24
        /// </summary>
        string Icon24 { get; } //Путь до иконки размера х24. Используется в выпадающем списке на странице логина, список появляется если реализовано более 5 провайдеров

        /// <summary>
        /// Путь до иконки 36x36
        /// </summary>
        string Icon36 { get; } // Путь до иконки размера х36. Используется на странице авторизации на кнопке провайдера, если реализовано менее 6 провайдеров

        /// <summary>
        /// Идентификатор провайдера
        /// </summary>
        Guid ProviderUid { get; }//уникальный идентификатор провайдера

        /// <summary>
        /// Используется ли провайдер
        /// </summary>
        bool CanUse();//Условие для проверки, что пользователь нажал на иконку именно этого провайдера. В качестве проверки может служить поиск параметров в запросе. Подробнее в примере

        /// <summary>
        /// Отправка запроса аутентификации внешнему провайдеру
        /// </summary>
        /// <param name="requestData">Данные запроса</param>
        /// <param name="redirectUrl">Url перенаправления</param>
        /// <returns>Url перенаправления</returns>
        string LogOn(object requestData, string redirectUrl);//Действие, которое будет выполняться при нажатии на иконку входа

        /// <summary>
        /// Действие после получения ответа от внешнего провайдера аутентификации
        /// </summary>
        /// <param name="requestData">Данные запроса</param>
        /// <param name="redirectUrl">Url перенаправления</param>
        /// <returns>Токен аутентификации пользователя</returns>
        string LogOnResponse(object requestData, string redirectUrl);//Действие, которое будет выполняться при получении ответа от внешней системы, когда пользователь ввел учетные данные для входа во внешнюю систему

        /// <summary>
        /// Получение пользователя
        /// </summary>
        /// <param name="token">Токен аутентификации во внешней системе</param>
        IUser UserByToken(string token);//Определение пользователя ELMA по пользователю внешней системы

        /// <summary>
        /// Получение информации об учетной записи внешнего провайдера аутентификации
        /// </summary>
        /// <param name="token">Токен аутентификации во внешней системе</param>
        object GetUserInfo(string token);//После того как пользователь авторизовался во внешней системе и в ELMA пришел токен, есть возможность запросить по токену данные о пользователе у внешней системы (mail, ФИО и тд)

        /// <summary>
        /// Добавить связь с учетной записью внешнего провайдера аутентификации
        /// </summary>
        /// <param name="requestData">Данные запроса</param>
        /// <param name="user">Пользователь системы ELMA</param>
        /// <returns>Url перенаправления. При возврате пустой строки будет перенаправлен на страницу профиля текущего пользователя</returns>
        string AddOAuth(object requestData, IUser user);//Осуществить привязку. Метод вызывается со страницы пользователя. В этом методе рекомендуется запросить информацию у внешнего провайдера, отобразить страницу авторизации (если пользователь не авторизован во внешней системе) 

        /// <summary>
        /// Действие после получения ответа на добавление информации о пользователе от внешнего провайдера аутентификации
        /// </summary>
        /// <param name="requestData">Данные запроса</param>
        void AddOAuthResponse(object requestData);//обработать ответ от внешнего провайдера. После прохождения авторизации обработать полученный токен пользователя

        /// <summary>
        /// Удалить связь с учетной записью внешнего провайдера аутентификации
        /// </summary>
        /// <param name="user">Пользователь системы ELMA</param>
        void RemoveOAuth(IUser user);//Очистить служебное поле токена данного провайдера авторизации

        /// <summary>
        /// Разрешено ли использование внешнего провайдера аутентификации
        /// </summary>
        /// <param name="user">Пользователь системы ELMA</param>
        bool OAuthAccepted(IUser user);// Разрешено ли пользователю использование внешнего провайдера аутентификации

        /// <summary>
        /// Установлена ли связь с учетной записью внешнего провайдера аутентификации
        /// </summary>
        /// <param name="user">Пользователь системы ELMA</param>
        bool OAuthExist(IUser user);//Есть ли связь между учетной записью в ELMA и внешним провайдером.
    }
}

Пример реализации

Предварительные настройки

Для реализации возможности авторизации в системе ELMA с помощью внешних систем аутентификации необходимо выполнить некоторые дополнительные настройки:

  • Для системного объекта «Пользователь» создать дополнительное свойство с типом данных «Строка» в котором будет храниться информация о токене авторизации через внешнюю систему (рис. 2). После создания и сохранения свойства необходимо опубликовать объект и перезапустить сервер.
Важно!
Данное поле не следует помещать на формы объекта, отображаемые в веб-части.

  • Поместить изображения, которые будут служить иконками входа/привязки в следующие папки системы:
    • Иконка 12*12 – <общая папка с системой ELMA> \Web\Content\Images\x12;
    • Иконка 24*24 – <общая папка с системой ELMA> \Web\Content\Images\x24;
    • Иконка 36*36 – <общая папка с системой ELMA> \Web\Content\Images\x36.

Пример сценария

В данной статье приведен пример сценария для настройки авторизации в системе ELMA через социальную сеть «Вконтакте».

Для реализации точки расширения необходимо подключить следующие сборки:

System.Web.Extensions
System.Web.Mvc
Elewise.ELMA.OAuth.Client
Elewise.ELMA.OAuth.Client.Web
Elewise.ELMA.Security
Elewise.ELMA.SDK.Web

Пространства имен:

using EleWise.ELMA.ComponentModel;
using EleWise.ELMA.OAuth.Client.Services;
using EleWise.ELMA.OAuthVK.Constants;
using EleWise.ELMA.OAuthVK.Models;
using EleWise.ELMA.OAuthVK.Web.Models;
using EleWise.ELMA.Runtime.Settings;
using EleWise.ELMA.Security.Managers;
using EleWise.ELMA.Security.Models;
using EleWise.ELMA.Security.Services;
using EleWise.ELMA.Serialization;
using EleWise.ELMA.Services;

Текст сценария:

namespace EleWise.ELMA.OAuthVK.Web.Components
{
    /// <summary>
    /// Аутентификация по учетной записи ВКонтакте
    /// </summary>
    [Component]
    public class VKOAuthProvider : IOAuthProvider
    {
        private IHttpContextAccessor httpContextAccessor;
        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="accessor"></param>
        /// <param name="authenticationService"></param>
        public VKOAuthProvider(IHttpContextAccessor accessor)
        {
            httpContextAccessor = accessor;
        }

        /// <summary>
        /// Переадресация ответа при аутентификации в ELMA, после аутентификации во внешней системе при попытке входа. Добавляем параметр с идентификатором провайдера. При использовании системного функционала необходимо ссылаться на метод LogOnResponse , контроллера OAuthClientController и указывать расположение EleWise.ELMA.OAuth.Client.Web.RouteProvider.AreaName (оставить как есть в примере )
        /// </summary>
        private string AuthCallback
        {
            get
            {
                return string.Format("{0}/{1}/OAuthClient/LogOnResponse?{2}={3}", CommonSettings.ApplicationBaseUrl, EleWise.ELMA.OAuth.Client.Web.RouteProvider.AreaName, "ELMAAuthProviderUid", new Guid("619C51E3-BD62-4E02-B3FB-209277B553DE"));
            }
        }

        /// <summary>
        /// Переадресация ответа при привязке учетной записи пользователя к учетной записи ВК, куда направляем в ELMA, после аутентификации во внешней системе при привязке учетных записей. Добавляем параметр с идентификатором провайдера.
        /// </summary>
        private string AddOAuthResponseCallback
        {
            get
            {
                return string.Format("{0}/{1}/OAuthClient/AddOAuthResponse?{2}={3}", CommonSettings.ApplicationBaseUrl, EleWise.ELMA.OAuth.Client.Web.RouteProvider.AreaName, "ELMAAuthProviderUid", new Guid("619C51E3-BD62-4E02-B3FB-209277B553DE"));
            }
        }

        /// <summary>
        /// Базовые настройки системы, из которых подставляем базовый URL в перенаправление, которое выполнено выше
        /// </summary>
        private CommonSettings CommonSettings
        {
            get
            {
                if (Locator.Initialized)
                {
                    var module = Locator.GetService<CommonSettingsModule>();
                    if (module != null)
                    {
                        return module.Settings;
                    }
                }
                return null;
            }
        }

        /// <summary>
        /// Метод для формирования строки запроса к ВК для получения токена и user_id
        /// </summary>
        /// <param name="code"></param>
        /// <param name="callback"></param>
        /// <returns></returns>
        private string AccessTokenRequest(string code, string callback)
        {
            return string.Format("https://oauth.vk.com/access_token?client_id={0}&client_secret={1}&code={2}&redirect_uri={3}", "7180076", "edAASvjXBAvqTDgLaLEY", code, string.IsNullOrWhiteSpace(callback) ? AuthCallback : callback);
        }

        /// <summary>
        /// Идентификатор провайдера
        /// </summary>
        public Guid ProviderUid
        {
            get
            {
                return new Guid("619C51E3-BD62-4E02-B3FB-209277B553DE");
            }
        }

        /// <summary>
        /// Иконка х16
        /// </summary>
        public string Icon16
        {
            get
            {
                return "#x16/vk16.jpg";
            }
        }

        /// <summary>
        /// Иконка х24
        /// </summary>
        public string Icon24
        {
            get
            {
                return "#x24/vk24.jpg";
            }
        }

        /// <summary>
        /// Иконка х36
        /// </summary>
        public string Icon36
        {
            get
            {
                return "#x36/vk36.png";
            }
        }

        /// <summary>
        /// Название провайдера
        /// </summary>
        public string ProviderName
        {
            get
            {
                return "VK";
            }
        }

        /// <summary>
        /// Метод определяет по запросу использовался ли этот провайдер при вызове формы авторизации во внешней системе. Определение проходит по параметру, который добавляется при формировании строки перенаправления (выше в коде AuthCallback)
        /// </summary>
        /// <returns></returns>
        public bool CanUse()
        {
            var requestUidS = httpContextAccessor.Current().Request.Params["ELMAAuthProviderUid"];
            if (!string.IsNullOrWhiteSpace(requestUidS))
            {
                Guid requestGuid;
                if (Guid.TryParse(requestUidS, out requestGuid))
                {
                    return requestGuid.Equals(ProviderUid);
                }
            }

            return false;
        }

        /// <summary>
        /// Направляем пользователя на страницу авторизации в ВК
        /// </summary>
        /// <param name="requestData">Параметры запроса</param>
        /// <param name="redirectUrl">Url адрес перенаправления</param>
        /// <returns></returns>
        public string LogOn(object requestData, string redirectUrl)
        {
            return string.Format("https://oauth.vk.com/authorize?client_id={0}&redirect_uri={1}", "7180076", AuthCallback);
        }

        /// <summary>
        /// Обрабатываем ответ, который прислал ВК, после того как пользователь ввел учетные данные
        /// </summary>
        /// <param name="requestData">Данные запроса</param>
        /// <param name="redirectUrl">Url перенаправления</param>
        /// <returns></returns>
        public string LogOnResponse(object requestData, string redirectUrl)
        {
            var controllerData = requestData as Controller;
            var authorizationCode = controllerData.Request.Params["Code"] as string;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(AccessTokenRequest(authorizationCode, null)));

            var responseText = GetResponseText(request);

            if (!string.IsNullOrWhiteSpace(responseText))
            {
                var oauthVkModel = new JsonSerializer().Deserialize<VKOauth.OAuthVKModel>(responseText);
                return oauthVkModel.user_id.ToString();
            }

            return string.Empty;
        }        

        /// <summary>
        /// Не используется в ВК
        /// </summary>
        /// <param name="token">Токен</param>
        public object GetUserInfo(string token)
        {
            return null;
        }

        /// <summary>
        /// Получить пользователя по токену
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public IUser UserByToken(string token)
        {
            return UserManager.Instance.Find(a => ((IUserConfigExt)a).Token == token).Single();
        }

        /// <summary>
        /// Направляем пользователя на страницу авторизации в ВК
        /// </summary>
        /// <param name="requestData">Данные запроса</param>
        /// <param name="user"></param>
        /// <returns></returns>
        public string AddOAuth(object requestData, IUser user)
        {
            var vkUser = user as IUserConfigExt;
            if (vkUser == null)
            {
                return null;
            }

            return string.Format("https://oauth.vk.com/authorize?client_id={0}&redirect_uri={1}", "7180076", AddOAuthResponseCallback);
        }

        /// <summary>
        /// Обрабатываем ответ после авторизации в ВК, забираем токен и user_id – прописываем их в служебное поле токена
        /// </summary>
        /// <param name="requestData"></param>
        public void AddOAuthResponse(object requestData)
        {
            var controllerData = requestData as Controller;
            var authorizationCode = controllerData.Request.Params["Code"] as string;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(AccessTokenRequest(authorizationCode, AddOAuthResponseCallback)));

            var responseText = GetResponseText(request);

            if (!string.IsNullOrWhiteSpace(responseText))
            {
                var oauthVkModel = new JsonSerializer().Deserialize<VKOauth.OAuthVKModel>(responseText);
                if (oauthVkModel != null)
                {
                    var user = AuthenticationService.GetCurrentUser<EleWise.ELMA.Security.Models.IUser>() as IUserConfigExt;
                    if (user != null)
                    {
                        user.Token = oauthVkModel.user_id.ToString();
                        user.Save();
                    }
                }
            }
        }

        /// <summary>
        /// Удалить привязку учетной записи ВК
        /// </summary>
        /// <param name="user">Пользователь</param>
        public void RemoveOAuth(IUser user)
        {
            var vkUser = user as IUserConfigExt;
            if (vkUser == null)
            {
                return;
            }
            vkUser.Token = "";
            vkUser.Save();
            return;
        }

        /// <summary>
        /// Разрешено ли использование аутентификации при помощи учетной записи ВК
        /// </summary>
        /// <param name="user">Пользователь</param>
        public bool OAuthAccepted(IUser user)
        {
            var vkUser = user as IUserConfigExt;
            if (vkUser == null)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Существует ли привязка у пользователя к учетной записи ВК
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public bool OAuthExist(IUser user)
        {
            var vkUser = user as IUserConfigExt;
            if (vkUser == null)
            {
                return false;
            }
            return !string.IsNullOrEmpty(vkUser.Token);
        }

        /// <summary>
        /// Получить текст ответа
        /// </summary>
        /// <param name="request">Запрос</param>
        private string GetResponseText(HttpWebRequest request)
        {
            var responseText = string.Empty;
            using (var response = (HttpWebResponse)request.GetResponse())
            {
                var encoding = Encoding.GetEncoding(response.CharacterSet);
                using (var responseStream = response.GetResponseStream())
                using (var reader = new StreamReader(responseStream, encoding))
                {
                    responseText = reader.ReadToEnd();
                }
            }
            return responseText;
        }

    }
}

Привязка учетной записи пользователя в системе ELMA к внешней системе

После реализации точки расширения в профиле пользователя в блоке Безопасность будет отображена ссылка для привязки учетной записи системы ELMA к учетной записи внешней системы (рис. 3).

Рис. 3. Профиль пользователя. Ссылка для привязки аккаунта сети «Вконтакте» к учетной записи пользователя

При нажатии на ссылку в первый раз будет отображено окно внешней системы с информацией о действиях, необходимых для привязки учетной записи ELMA к профилю пользователя в данной системе. Для разных систем данное окно будет отличаться. Следуйте подсказкам на экране для завершения привязки. На рисунке 4 приведен пример окна привязки к аккаунту в социальной сети «Вконтакте». В данном случае для завершения процесса необходимо нажать на кнопку Разрешить. Следует отметить, что данное окно отображается только при первой привязке учетной записи к внешней системе, в случае удаления связи и повторной привязки данное окно отображено не будет.

Рис. 4. Окно привязки учетной записи ELMA к аккаунту в социальной сети «Вконтакте»

Вход с помощью учетной записи внешней системы

Для того, чтобы войти в систему ELMA с помощью привязанной учетной записи внешней системы в окне авторизации в правом нижнем углу нажмите на кнопку и в выпадающем списке способов авторизации выберите Внешний провайдер. Окно авторизации примет вид, представленный на рис. 5.

Рис. 5. Окно авторизации в системе ELMA при помощи учетных записей внешних систем

Для входа в систему ELMA следует нажать на необходимую иконку внешней системы.

При нажатии на иконку браузер проверяет аутентификацию во внешней системе. Если вход во внешнюю систему был произведен, то будет произведен вход в систему ELMA, если вход во внешнюю систему не осуществлен будет отображено окно аутентификации во внешней системе. Данное окно может быть разным в зависимости от выбранной внешней системы, на рис. 6 представлен пример аутентификации в социальной сети «Вконтакте».

Рис. 6. Окно аутентификации в социальной сети «Вконтакте» для входа в систему ELMA

Для входа в систему ELMA необходимо пройти авторизацию в соответствующей системе.

Удаление связи учетной записи ELMA и внешней системы

Для того, чтобы удалить связь учетной записи ELMA и внешней системы необходимо в профиле пользователя в блоке Безопасность нажать на ссылку удаления связи со внешней системой (рис. 7).

Рис. 7. Профиль пользователя. Ссылка для удаления связи учетной записи ELMA и внешней системы

Следует отметить, что при реализации данного метода иконка входа с помощью внешней системы будет доступна всем пользователям, независимо от того установлена ли связь между учетной записью пользователя в системе ELMA и учетной записью во внешней системе. При этом при попытке входа в систему ELMA с помощью иконки внешней системы без установленной привязки со внешней системой будет отображена ошибка (рис. 8).

Рис. 8. Ошибка входа в систему ELMA с помощью аутентификации во внешней системе