[ELMA3] Авторизация в системе ELMA при помощи внешних провайдеров аутентификации
Система ELMA поддерживает возможность авторизации пользователей через внешние системы аутентификации, например, "ВКонтакте", Google, Facebook и т. д.
Пример отображения данных
Рис. 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 с помощью внешних систем аутентификации необходимо выполнить некоторые дополнительные настройки.
1. Для системного объекта "Пользователь" создать дополнительное свойство с типом данных "Строка" в котором будет храниться информация о токене авторизации через внешнюю систему (рис. 2). После создания и сохранения свойства необходимо опубликовать объект и перезапустить сервер.
Рис. 2. Свойство для хранения информации о токене авторизации через внешнюю систему
2. Поместить изображения, которые будут служить иконками входа/привязки в следующие папки системы:
- Иконка 12*12 – <общая папка с системой ELMA> \Web\Content\Images\x12;
- Иконка 24*24 – <общая папка с системой ELMA> \Web\Content\Images\x24;
- Иконка 36*36 – <общая папка с системой ELMA> \Web\Content\Images\x36.
Пример сценария
В данной статье приведен пример сценария для настройки авторизации в системе ELMA через социальную сеть "ВКонтакте".
До реализации точки расширения нужно создать служебную модель данных, получаемых из внешнего источника, в данном случае сайта "ВКонтакте".
Пространства имен:
using System;
using System.Collections.Generic;
using System.Linq;
using EleWise.ELMA.API;
using System.Text;
Текст сценария:
namespace VKOAuth
{
public class OAuthVKModel
{
/// <summary>
/// Токен
/// </summary>
public string access_token { get; set; }
/// <summary>
/// Время истечения срока годности
/// </summary>
public long expires_in { get; set; }
/// <summary>
/// Иденификатор пользователя
/// </summary>
public long user_id { get; set; }
}
}
Для реализации точки расширения необходимо подключить следующие сборки:
System.Web.Extensions
System.Web.Mvc
Elewise.ELMA.OAuth.Client
Elewise.ELMA.OAuth.Client.Web
Elewise.ELMA.Security
Elewise.ELMA.SDK.Web
Пространства имен:
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Net;
using System.Web.Mvc;
using EleWise.ELMA.API;
using System.Text;
using EleWise.ELMA.ComponentModel;
using EleWise.ELMA.OAuth.Client.Services;
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 VKOAuth
{
/// <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.OAuthVK.Web", "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.OAuthVK.Web", "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<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<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 с помощью аутентификации во внешней системе