Урок 2. Аутентификация пользователей в программе
Last updated
Last updated
Так как мобильное приложение расширяет возможности десктопного или web-приложения, то оно должно поддерживать многопользовательский режим. При реализации процесса аутентификации пользователей в мобильном приложение важно в первую очередь ответить на вопрос: кто будет пользоваться этим приложением?
Если пользователями приложения будут внутренние сотрудники, то экран входа можно реализовать тем же образом, что делали для десктопного приложения. Пользователь выбирает себя из выпадающего списка и в текстовое поле вводит пароль. Если пользователями будут внешние клиенты, то вход в приложение можно сделать по номеру телефона или электронной почте с отправкой кода для входа.
В качестве учебного проекта будем делать мобильное приложение для внутренних пользователей. Регистрацию внешних пользователей рассмотрим в дополнительных материалах. Т.к. в таком случае нужно регистрироваться на стороннем сервисе, предоставляющем услуги по отправке уведомлений на устройство
Аутентификация - процедура проверки подлинности, например проверка подлинности пользователя путем сравнения введенного им пароля с паролем, сохраненным в базе данных.
Авторизация - предоставление определенному лицу или группе лиц прав на выполнение определенных действий.
При реализации экрана входа нам понадобятся иконки. Скачайте архив с изображениями и разархивируйте его в папку проекта \Template\Projects\1. Template\MobileForms\Images.
Далее вспомним, как хранятся данные пользователей в базе данных и процесс аутентификации пользователей в программе.
В базе данных в таблице public.user хранится общий список пользователей системы:
Описание полей таблицы:
user_id - идентификатор глобального пользователя;
user_name - логин глобального пользователя;
user_full_name - полное имя глобального пользователя;
person - признак, определяющий, является ли данный глобальный пользователь реальным пользователем;
enabled - признак, определяющий, является ли данный глобальный пользователь включенным;
language_id - идентификатор языка глобального пользователя;
time_zone_info_id - идентификатор временной зоны глобального пользователя;
user_password - хеш пароля глобального пользователя.
В таблице есть записи системных пользователей:
Служба Workflow Engine ($workflow_engine$) - системный пользователь службы Workflow Engine, от имени которого совершаются некоторые автоматические действия с данными в базе данных;
WS. Гость (WS_GUEST) - системный пользователь, под которым запускаются клиентское приложение для десктопа и мобильное приложение.
Пользователи Администратор, Пользователь 1, Пользователь 2 и Пользователь 3 - реальные пользователи, под которыми можно работать в программе.
Таблица template.user содержит идентификаторы пользователей, которые имеют доступ к бизнес-процессу Template, а так же флаг access_to_mobile_app доступа к мобильному приложению.
Так же в эту таблицу можно добавить поля для индивидуальных пользовательских настроек, например, для доступа к какому-нибудь отчету.
Здесь стоит сказать о том, что WT-приложение может объединять несколько программ для разных бизнес-процессов, например, для автомойки и шиномонтажа.
Каждая программа будет иметь свой набор xml-файлов форм и свой серверный xml-файл с запросами. И, в большинстве случаев, серверная часть у них будет одна - нет смысла специально разделать серверные части, если в этом нет потребности. А значит, и базу данных можно использовать одну, но для каждого бизнес-процесса делать отдельную схему, чтобы разделить таблицы и функции.
Несмотря на то, что автомойка и шиномонтаж разные программы, какие-то пользователи могут иметь доступ к обеим программам, например, администраторы. Таким образом, в таблице public.user будут храниться логин и пароль, а так же пользовательские настройки, общие для автомойки и шиномонтажа. А в таблицах carwash.user (автомойка) и tireservice.user (шиномонтажа) будут храниться настройки пользователей, характерные для каждой программы. Например, доступ к какому-нибудь отчету.
Первым делом необходимо получить список активных пользователей с доступом к мобильному приложению. Для этого создается отдельный SQL-запрос, который добавляется в Permission доступный для группы GuestGroup.
По умолчанию все запросы к серверу подписываются гостевой учеткой WS_GUEST (находится в группе пользователей GuestGroup), логин и пароль этой учетки зашиты в мобильное приложение WS и не могут быть изменены. В одном из будущих уроков рассмотрим, как персонализировать свое приложение для публикации в магазинах приложений и уделим внимание настройке конфига.
Полученный список пользователей отображается в выпадающем списке на экране входа:
После того, как пользователь выбрал учетную запись и ввел пароль, мобильное приложение хеширует пароль безопасным алгоритмом SHA-512 и отправляет на сервер запрос с данными в зашифрованном виде.
Когда запрос приходит на сервер, он попадает сначала в веб-сервер Kestrel, на котором запущена веб-служба, а затем перенаправляется в серверное приложение.
Kestrel представляет кроссплатформенный веб-сервер и по умолчанию включается в проект ASP.NET Core.
На сервере механизм аутентификации и авторизации реализован с помощью JWT-токенов. Когда Workflow Engine получает запрос на аутентификацию пользователя, полученные логин и хеш пароля сверяются с теми, которые хранятся в таблице public.user в базе данных. Если логин и хеш пароля совпали - генерируется JWT-токен, который возвращается клиентскому приложению вместе с временем жизни этого токена и одноразовым токеном для повторной генерации основного JWT-токена.
JWT (или JSON Web Token) представляет собой веб-стандарт, который определяет способ передачи данных о пользователе в формате JSON в зашифрованном виде.
Мобильное приложение хранит JWT-токен и подписывает им все последующие запросы к серверу. По истечении времени жизни JWT-токена клиентская часть отправляет запрос на обновление JWT-токена.
Для начала скорректируем таблицу пользователей (template.user), добавив признак доступа к мобильному приложению - не все пользователи WT-программы могут иметь доступ к мобильному приложению:
Выполним запрос, чтобы дать доступ всем активным пользователям:
Скорректируем представление template.user_info, добавив в него новое поле:
Добавим SQL-запрос в серверный xml-файл
В существующий BaseViewPermission добавим новый запрос. Это разрешение по умолчанию добавляется во все группы пользователей. Напомним, что в учебном проекте используются динамические права доступа, когда пользователи-администраторы через интерфейс программы назначают права группам пользователей.
Первым делом переименуем существующие файлы: TemplateEmptyStart.xml в TemplateLogin.xml (не забудьте внести правки в таблицу public.mobile_app), а из TemplateEmptySettings.xml удалим Empty. Так же внесем изменения в атрибут Name
тэга <Form>
обоих файлов форм: Для экрана входа укажем TemplateLoginForm, а для экрана настроек - TemplateSettingsForm.
Давайте перейдем в редактор и откроем xml-файл стартового экрана (TemplateLogin.xml).
В файле экрана входа создайте первичное соединение с данными UserPrimaryGetDataConnection для получения списка пользователей из sql-запроса AppUserLoginSelectSqlQuery.
Заменим код объекта LogoPictureBox следующим кодом:
Здесь немного изменили размер объекта, а в тэге <Image>
указали выбор файла логотипа по условию AppThemeDarkEqualCondition.
Для выбора пользователя создайте объект с именем UserComboBox типа ComboBox, сделав отступ от LogoPictureBox в 54 единицы и привязав ширину и координату Left к аналогичным значениям LogoPictureBox. В атрибуте Show
тэга <NullValue>
укажите значение False - нам не нужно добавлять в список пустое значение, которое будет отображаться, если значение в списке не выбрано. Вместо этого будем использовать текст-подсказку из тэга <Text>
, в котором укажите текст "Выберите пользователя". В тэге <ValueList>
укажите UserPrimaryGetDataConnection.
В тэге <ForeColor>
выпадающего списка будем использовать цвет в зависимости от выбранного оформления системы. Для темной темы нужно использовать светлый цвет (WhiteSmoke - уже есть на форме), а для светлой темы - темный цвет (MostlyBlack - так же есть на форме). В тэге <BackColor>
так же будем отслеживать оформление системы: для темной темы будем использовать MostlyBlack, а для светлой темы - WhiteSmoke.
Добавьте условие UserComboBoxIsNullCondition, которое будет проверять наличие значения в UserComboBox.
Для ввода пароля добавьте объект с именем PasswordTextBox типа TextBox, задав отступ от UserComboBox в 8 единиц, а ширину и координату Left равными соответствующим свойствам списка. В тэге <TipText>
задается текст-подсказка, который будет отображаться в поле, если значение объекта Null, - укажите "Пароль". А в тэге <TipTextColor>
задается цвет текста-подсказки. Его также будем выбирать в зависимости от оформления системы: для темной темы будем использовать DarkGray, а для светлой темы - LightGray. Для тэгов <ForeColor>
и <BackColor>
задаем значения такие же, как указывали для объекта UserComboBox.
Принято чтобы пароль при вводе скрывался звездочками или точками, а у пользователя была возможность проверить введенные символы, отключая режим пароля у текстового поля.
Для отслеживания активности режима пароля, создадим переменную ShowPasswordVariable:
В тэге <Password>
текстового поля PasswordTextBox укажем новый объект.
Для переключения режима пароля создадим объект ShowPasswordPictureBox, который на экране будет представлен в виде классической иконки глаза:
Обратите внимание на то, как задали значение в тэге <Right>
: на экране объект ShowPasswordPictureBox будет отрисовываться поверх объекта PasswordTextBox.
Объекты типа PictureBox не поддерживают вызовы команд, поэтому будем отслеживать событие тапа по объекту, используя условие ClickCondition:
Для изменения значения переменной ShowPasswordVariable будем использовать команду типа ValueSetCommand:
Команду будем вызывать в Execution по условию ShowPasswordPictureBoxClickCondition:
Добавьте на экран кнопку LoginButton, сделав отступ от поля ввода пароля в 36 единиц, а левую координату и ширину кнопки привяжите к соответствующим значениям объекта PasswordTextBox. Для тэга <BackColor>
будем использовать один цвет ColorPrimary вне зависимости от выбранной темы оформления. А цвет текста на кнопке (тэг <ForeColor>
) будут зависеть от свойства Enabled самой кнопки. Если кнопка доступна, то цвет текста будет WhiteSmoke, иначе - DarkGray. Для задания радиуса скругления углов кнопки используется тэг <BorderCorner>
со значением 5.
Кнопка "Войти" должна быть активна если выбран пользователь и поле пароля содержит символы:
Запустите приложение и проверьте реализованную логику:
Отлично! Теперь можем приступить к реализации аутентификации.
Для аутентификации в программе используется команда типа LoginCommand. Создадим эту команду:
В команде используется UserSecondaryGetDataConnection типа SecondaryGetDataConnection, который фильтрует список по идентификатору из UserComboBox и возвращает данные выбранного пользователя.
При вызове команда сама вычислит хеш строки пароля и отправит на сервер логин пользователя и хеш пароля в зашифрованном виде.
Создадим условия проверки результата выполнения команды авторизации:
Создадим команду вывода сообщения об ошибке аутентификации:
Создадим необходимые Execution для обработки результатов выполнения команды авторизации:
Где команда MainFormShowCommand открывает главный экран приложения:
Создайте пустую форму, которую будем использовать в следующем уроке для построения главного экрана мобильного приложения.
Для создания формы можете использовать паттерн из архива:
Мобильное WT-приложение предполагает подключение к сети во время использования, чтобы иметь возможность обмениваться данными с сервером. Если по каким либо причинам подключение отсутствует, то необходимо об этом уведомить пользователя. Для проверки доступа к Интернету используется универсальное значение <Info>
со значение ConnectivityIsConnected в атрибуте Type
.
Где команда InternetIssueMessageBoxCommand имеет вид:
Если на форме настроек изменили адрес сервера, то мобильное приложение настроится на новый сервер, но данные на экране входа не обновятся самостоятельно. Для их обновления создадим команду UserPrimaryDataConnectionRefreshCommand типа DataConnectionRefreshCommand для обновления UserPrimaryGetDataConnection. Помимо обновления списка пользователей, необходимо сбрасывать выбранное значение в выпадающем списке UserComboBox и введенные символы в поле PasswordTextBox.
Объединим новые команды в одну:
В файле формы уже описан Execution, который проверяет результат выполнения команды SettingsFormShowCommand и вызывает команду LogoPictureBoxRestartValueSetCommand на изменения изображения в объекте LogoPictureBox.
Замените имя вызываемой команды на RefreshSequentialCommand:
Удалите из файла описание команды LogoPictureBoxRestartValueSetCommand - она нам больше не пригодится.
На уроке мы рассмотрели процесс аутентификации пользователя, .
В архиве присутствуют xml-файлы форм для мобильного приложения и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.
Архив не содержит xml-файлы форм десктопного приложения.
В папке \Template\Projects\1. Template\Patterns создайте две вложенные папки Desktop и Mobile. В первую перенесите все существующие паттерны (если такие есть), а во вторую распакуйте архив с шаблонами из урока. Как подключить шаблоны к проекту описано в . Если в редакторе остался проект для desktop-приложения, скорректируйте путь до его шаблонов с учетом изменения расположения файлов паттернов.