Простая реализация Token для взаимодействия мобильного приложения с WebAPI

С недавнего времени занялся разработкой мобильных приложений с помощью 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;
         }
      //если всё ОК делаем что надо
   }

Как мне кажется полученная реализация псевдо-токена имеет право на существование и кому-то может помочь в реализациях. Надеюсь, сообщество палками меня не закидает, это мой первый пост, надеюсь что будет не только критика.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 13

    +2
    Ох, зря Вы так… Ведь и правда закидают.
    Ваша реализация крайне небезопасна. С точки зрения механизма, у вас не токен, а сессионная кука.
    Но хуже всего сама аутентификация. Серьёзно, поиск по частичному совпадению логина и пароль с чистом виде?
      0
      уж буду переделывать, по требованию СБ для выкладывания наружу — SSL + Token привязанный к устройству, но просто велосипед изобретался из-за желания простоты реализации и по причине того что в соответствии с мыслями, примеров не нашёл в сети :)
      +1
      использования каких-нибудь пакетов из NuGet, чего хотелось бы по максимуму избежать

      а в чем проблема использования пакетов из NuGet?
        –2
        не люблю лишнее :) взять тот же пакет WinSCP… запарило… по непонятным причинам (не трогая NuGet) делаю релиз приложения WinForm выкладываю на ресурс, а выскакивает ошибка несоответствия версий между EXE и DLL… потому и не люблю лишнее… только от безысходности :)
          +2
          т.е. Вы считаете, что это достаточный аргумент для написания велосипеда, вместо использования библиотеки, которая обкатана во многих проектах? а как же быть с .net core, там где Framework по NuGet пакетам распилили? будете весь Framework переписывать чтобы избежать использования NuGet?
        0
        Для авторизации есть OAuth 2.0.
        Для аутентификации — OpenID Connect 1.0.
        Зачем изобретать изобретённое?
        В случае дотнета — есть IdentityServer, который реализует оба протокола.
          0
          Такие штуки я писал лет 15 назад :)))
            +3
            Эх огорчу вас с вашим велосипедом, в ASP.Net (что в Core, что в классическом) уже есть возможность работы с JWT Bearer токенами. В вашем велосипеде придется в каждый акшн добавлять проверку токена, когда при использовании JWT, просто добавить атрибут [Authorize] к контроллеру и все. Плюс, к тому токену можно прицепить кучу инфы(данные пользователя, время действия токена и тд.). А ваше решение даже безопасным нельзя назвать.
              0
              Зря вы используете нестандартный заголовок HTTP без префикса X-, это может однажды плохо кончиться. Надо или использовать заголовок X-Token вместо Token, или воспользоваться стандартным `Authorization: Bearer ...` либо полустандартным `Authorization: Token ...`.

              Ну и аутентификацию надо делать фильтрами, а не писать один и тот же код в каждом методе.
                0
                Рекомендация использовать X-префиксы устарела в 2012 году с выходом RFC6648.

                Creators of new parameters to be used in the context of application
                protocols:

                1. SHOULD assume that all parameters they create might become
                standardized, public, commonly deployed, or usable across
                multiple implementations.

                2. SHOULD employ meaningful parameter names that they have reason to
                believe are currently unused.

                3. SHOULD NOT prefix their parameter names with «X-» or similar
                constructs.
                +1
                Оказывается пока приложение активно в App.Current.Properties могут хранится любые объекты, в том числе и объекты с данными собственных классов, но при «убиении» процесса если там было что-то отличное от «простых» объектов — содержимое App.Current.Properties отчищается, но если там хранить только простые объекты — string, bool, int и т.п. то всё останется сохраненным!

                Я не работал с Xamarin, но мне кажется, что тут дело в возможности сериализации объектов. Вы не пробовали пометить свои кастомные классы атрибутом Serializable?

                  0
                  пробовать не пробовал, но вопрос — почему они «живут» пока работает приложение, и уничтожаются при убиении процесса приложения?.. при тех же данных — если в проперти «простые» объекты — уничтожения нет…
                    +1
                    вероятно, причина в том, что .net знает, как сохранять «простые» объекты, а как быть с Вашими — нет

                Only users with full accounts can post comments. Log in, please.