С недавнего времени занялся разработкой мобильных приложений с помощью Xamarin.Forms в связи с производственной так сказать необходимостью. Не буду конечно рассказывать про танцы с бубнами чтобы написать и запустить на эмуляторе приложение «Hello, World!», но главное разработка пошла достаточно плавно.
Благо и понимание задачи было — а именно — взаимодействие мобильного приложения с базой данных внутренней CRM системы в компании, добавить сотрудникам мобильности, но при этом не забывать и о безопасности. Было принято решение создать WebAPI, ибо чтобы работать с уже привычными ASMX веб-сервисами в Xamarin нужно плясать с бубнами.
Как сказал выше, в том числе хотелось сделать «связующее звено» достаточно безопасным, а значит мобильное приложение должно иметь авторизацию (до кучи и удобства с возможностью сохранения авторизации и автоматического входа.
Не хотелось глубоко копаться в реализации WebAPI с авторизацией на уровне Token, а хотелось сделать что-то попроще, тем более пока «гуглил» видел что такого желания у людей с избытком, но все отсылы отвечающих были либо к стандартным механизмам, либо использования каких-нибудь пакетов из NuGet, чего хотелось бы по максимуму избежать.
В базе собственной CRM и так уже есть вся информация для авторизации и городить что-то лишнее тупо не хотелось.
В итоге, после долгих мытарств, поисков и т.п. — думаю у меня получилось достаточно неплохое решение, которым мне и хочется поделится с сообществом.
Итак, что мы имеем:
Авторизация и получение Token
Не буду опять-таки вдаваться в подробности создания WebAPI, приведу пример кода функции авторизации.
[HttpPost]
public LoginResult Logining([FromBody] LoginInfo LogInf)
{
if (dc.Managers.Count(w => w.EMail.StartsWith(LogInf.PortalUserEMail)) > 0)
{
if (dc.Managers.Count(w => w.EMail.StartsWith(LogInf.PortalUserEMail) && w.Password == LogInf.PortalUserPassword) > 0)
{
Managers _man = dc.Managers.First(w => w.EMail.StartsWith(LogInf.PortalUserEMail));
_man.Token = GenerateToken();
dc.SubmitChanges();
return new LoginResult()
{
Error = null, Token = _man.Token, ValidUser = true,
managerInfo = new ManagerInfo() { ManagerId = _man.Id, ManagerName = _man.ManagerName }
};
}
else
{
return new LoginResult()
{
Error = "Неверный пароль!", Token = null, ValidUser = false, managerInfo = null
};
}
}
else
{
return new LoginResult()
{
Error = "Такого пользователя не существует!", Token = null, ValidUser = false, managerInfo = null
};
}
}
Объяснять что делает функция думаю тоже нет смысла, но как результат при вызове API мобильное приложение получает информацию что юзер валиден и выдаёт сгенерированный Token (я решил не мелочится и заложил генерацию 1000-символьной строки из большого количества символов всей английской и русской клавиатуры, с заглавными и строчными буквами, цифр и простых символов.
Этот «псевдо»-Token я прописываю в
App.Current.Properties["Token"] = rez.Token;
приложения.
Кстати стоит, как мне кажется, отдельно отметить 3 потраченных дня, откаты версий и т.п. чтобы разобраться с этим самым App.Current.Properties.
Произошла ситуация, что в какой-то момент при перезапуске приложения на эмуляторе содержимое App.Current.Properties отсутствовало. Долго мучился и пытался понять почему всё пропадает.
Оказывается пока приложение активно в App.Current.Properties могут хранится любые объекты, в том числе и объекты с данными собственных классов, но при «убиении» процесса если там было что-то отличное от «простых» объектов — содержимое App.Current.Properties отчищается, но если там хранить только простые объекты — string, bool, int и т.п. то всё останется сохраненным!
Продолжим. Все последующие обращения к API я снабжаю дополнительным заголовком:
var client = new HttpClient();
var address = $"http://хх.ххх.х.хх/SomeWebApi/api/Works?ManId=" + ManagerId;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("Token", (string)App.Current.Properties["Token"]);
HttpResponseMessage response = await client.GetAsync(address);
Что по сути к заголовкам запроса добавляет хидер. Теперь все остальные контроллеры WebAPI перед тем как что-либо «выдать» клиенту проверяют наличие и соответствие псевдо-Token.
К примеру:
public IEnumerable<WorkInformation> Get([FromUri] int ManId)
{
IEnumerable<string> UserToken;
if (!Request.Headers.TryGetValues("Token", out UserToken))
{
return null;
}
Managers _CurrManager = dc.Managers.First(w => w.Id == ManId);
List<WorkInformation> _list = new List<WorkInformation>();
if (_CurrManager.Token != UserToken.ToArray()[0])
{
_list.Add(new WorkInformation() { id = "-1", Client = "Ошибка проверки ключа защиты", DTime = DateTime.Now, Comment = "Перезайдите в приложение", EventImageName = "" });
return _list;
}
//если всё ОК делаем что надо
}
Как мне кажется полученная реализация псевдо-токена имеет право на существование и кому-то может помочь в реализациях. Надеюсь, сообщество палками меня не закидает, это мой первый пост, надеюсь что будет не только критика.
Автор: IT-AleX