Содержание статьи
Профиты многофакторной аутентификации для корпоративных приложений
- Более надежная аутентификация, чем только имя пользователя и пароль. К «что-то, что я знаю» (логин/пароль) добавляется «что-то, чем обладаю» — физическое устройство, а проверить, владеет ли им данный пользователь, позволяет код в СМС, отправленной на это устройство, или звонок на него с запросом пин-кода.
- Удобное управление настройками подобной системы аутентификации — включение/выключение разной степени защиты для разных пользователей, настройка различных политик и способов работы дополнительных факторов аутентификации.
- Возможность использовать учетные данные корпоративной инфраструктуры для входа в приложения, а не создавать новые логины и пароли для приложения в дополнение к корпоративным.
Мы рассмотрим реализацию этой задачи с помощью следующих продуктов и технологий:
- Azure Active Directory — облачный сервис многофакторной аутентификации;
- Azure Active Directory Authentication Libraries (ADAL) — библиотеки, обеспечивающие вызовы сервисов многофакторной аутентификации, включая проверки сертификатов, защищенное соединение с облачным сервисом аутентификации, отображение необходимых диалогов аутентификации. Поддерживает множество платформ и языков программирования;
- Xamarin Forms — кросс-платформенное средство создания мобильных приложений, обеспечивающее компиляцию в нативный код и стопроцентный доступ к нативным API. Особенно удобно для быстрого создания приложений с переиспользованием 80–90% и более исходного кода между iOS, Android, Windows UWP — как раз наш случай для данного примера. Xamarin доступен как бесплатная часть Microsoft Visual Studio во всех ее версиях, включая бесплатную Visual Studio Community Edition.
Также с помощью Active Directory Federation Services (ADFS) и их интеграции с Azure Active Directory мы можем использовать для многофакторной аутентификации корпоративные учетные записи и пароли внутреннего домена Active Directory, но основной упор в этой статье сделаем на первые две задачи. ADFS будет опциональной задачей, так как, если нам не нужна интеграция с локальным доменом Active Directory, мы можем создать учетные записи вручную непосредственно в сервисе Azure Active Directory.
Начнем с настройки Azure Active Directory: если у нас нет учетной записи и доступа к Microsoft Azure, можем легко получить бесплатную пробную учетную запись здесь. В рамках пробной подписки большое число сервисов предоставляется бесплатно на год. В целом все, что описано в данной статье, ты можешь попробовать бесплатно.
Регистрация мобильного приложения для доступа к Azure Active Directory
Первым шагом мы регистрируем мобильное приложение в Azure Active Directory для того, чтобы получить доступ к сервису. Фактически мы создаем учетную запись приложения с определенными идентификаторами и настройками безопасности. Только мобильное приложение, которое через защищенное соединение сможет предъявить данные идентификаторы, получит доступ к службе аутентификации.
Зайдя на портал Azure, в самой левой черной панели служб выбираем службу Azure Active Directory (или нажимаем All Resources и пользуемся поиском служб), переходим в подраздел App registrations и регистрируем новую учетную запись приложения, нажимая New application registration.
Xakep #230. Социальная инженерия
Заполняем поля новой учетной записи приложения:
- Name — любое имя, по которому потом нам самим будет понятно, о каком приложении речь;
- Application Type — выбираем Native, так как у нас будет мобильное приложение, а не веб-сервис;
- Redirect URI — это фактически идентификатор, который должен обязательно совпадать в настройке учетной записи приложения на стороне сервиса с настройкой на стороне клиента. Это может быть любой произвольный URI (произвольная строка в формате URI) — он не должен быть обязательно реальным хостом и не должен быть зарегистрирован в DNS.
Нажимаем Create и получаем новую учетную запись приложения с автоматически назначенным системой идентификатором, который называется APPLICATION ID.
Это очень важный идентификатор — его обязательно должно передать мобильное приложение вместе с совпадающим с настройкой сервиса Redirect URI, чтобы получить доступ к службе аутентификации.
Создание пользователя и включение многофакторной аутентификации
Теперь мы создадим пользователя, пароль и включим для него многофакторную аутентификацию.
Напомню, что, если есть задача использовать учетные записи, уже существующие в организации в локальном домене Active Directory, это можно сделать с помощью службы Active Directory Federation Services. Тогда нам не нужно будет заводить пользователей, можно будет настраивать многофакторную аутентификацию для уже существующих в домене пользователей, и при входе в мобильное приложение они будут использовать свои доменные логины и пароли.
То есть при использовании AFDS получится такая топология:
Мы же продолжим базовый сценарий, который не требует наличия домена Active Directory и настройки службы ADFS, и заведем пользователя самостоятельно в Azure Active Directory.
Заходим в знакомый раздел Azure Active Directory → Users and groups → All users и нажимаем New user.
Далее заполняем информацию о пользователе. Обрати внимание на User name: если у тебя в подписке Azure сконфигурировано свое доменное имя — используй его, если нет — система автоматически сгенерирует доменное имя в алиасе пользователя по принципу [логин нашей учетной записи Azure].onmicrosoft.com. Для целей тестирования этого достаточно, для производственных систем, очевидно, мы пропишем в подписке Azure свой более симпатичный домен. Также придумываем и прописываем пароль. На скриншоте подчеркнут автоматически сгенерированный домен для моей пробной подписки.
Нажимаем Create и получаем новую учетную запись пользователя.
Теперь включим для нее многофакторную аутентификацию. Заходим в раздел All users и нажимаем кнопку Multi-Factor Authentication.
Дальше мы попадаем в настройки многофакторной аутентификации, выбираем нужного пользователя и нажимаем Enable.
Мы должны получить сообщение об успешной инициализации многофакторной аутентификации для данного пользователя.
На стороне сервиса все готово — данный пользователь теперь будет аутентифицироваться многофакторно. Если хочется изменить настройки сервиса, можно зайти в раздел Service settings — сразу под надписью Multi-factor authentication на предпоследнем экране выше.
Здесь есть, например, интересный раздел, который задает, какие методы дополнительных «факторов» доступны пользователю.
Здесь можно настроить звонок либо СМС на телефон или подтверждение через мобильное приложение Microsoft Authenticator.
Конфигурирование сервиса Azure Active Directory завершено, теперь создадим мобильное приложение и интегрируем в него многофакторную аутентификацию.
Интеграция многофакторной аутентификации в мобильное приложение
Как я сказал выше, мы для примера возьмем Xamarin Forms (бесплатная часть Visual Studio), который позволит нам, переиспользуя большую часть кода, получить сразу нативные мобильные приложения с поддержкой многофакторной аутентификации для iOS, Android и Windows UWP.
Быстрый способ — используем готовый исходный код
- Забираем код из моего репозитория на GitHub.
- Прописываем в код приложения в файле MainPage.xaml.cs идентификаторы, которые мы сконфигурировали при регистрации приложения в облачном сервисе Azure Active Directory. Их полное соответствие в приложении и сервисе необходимо, чтобы сервис не отклонял обращения приложения.
clientID
— это Application ID из наших настроек облачного сервиса выше.
public static string clientId = "<<INSERT YOUR CLIENT ID HERE>>";
returnURI
— это Redirect URI из наших настроек облачного сервиса выше.
public static string returnUri = "http://MFATestPCL-redirect";
Все готово. Теперь мы рассмотрим также создание приложения с нуля.
Детальный способ — создаем приложение с нуля
Запускаем Visual Studio (я сейчас использую VS 2017, версия 15.6.7) с установленным Xamarin. Это можно проверить, запустив Visual Studio Installer и нажав Modify текущей инсталляции Visual Studio.
Xamarin входит во все версии Visual Studio — даже в бесплатную Community Edition, но нужно убедиться, что он включен и установлен, как показано выше.
Запускаем Visual Studio и создаем новый проект: меню File → New → Project. Выбираем Cross-Platform → Mobile App (Xamarin.Forms).
Далее выбираем Blank App и .NET Standard как Code Sharing Strategy.
Получив новый проект, проверим запуск мобильного приложения, по умолчанию стартовый проект выделен жирным шрифтом — Android-приложение.
Если это не так, сделаем его стартовым: правый клик на проекте App.Android и меню Set as a StartUp Project.
Нажав F5, запустим приложение на эмуляторе Android и увидим:
Убедившись, что все работает, останавливаем отладку (Shift + F5).
Чтобы интегрировать многофакторную аутентификацию, подключим библиотеку Azure Active Directory Authentication Libraries (ADAL). Подключить ее надо будет к каждому проекту: правый клик на каждом проекте — всего четыре проекта (общий + специфичный под каждую из трех платформ) = 4 раза → Manage NuGet Packages.
Далее закладка Browse — вводим в поиск adal и подключаем, нажимая на кнопку со стрелкой вниз.
Для реализации логики работы с библиотекой будем использовать следующий подход:
- в общем проекте определим интерфейс IAuthenticator, через который будем вызывать аутентификацию из общего кода;
- в каждом из платформенных проектов определим специфичную для конкретной платформы реализацию взаимодействия аутентификации с пользовательским интерфейсом этой платформы;
- используя DependencyService, доступный в Xamarin Forms, мы сможем при вызове единого метода Authenticate интерфейса IAuthenticator из общего кода автоматически использовать именно ту реализацию под конкретную платформу, на которой в данный момент запущено приложение.
Начинаем с определения интерфейса IAuthenticator в общем для всех платформ проекте, создаем в нем новый файл IAuthenticator.cs с определением интерфейса (правый клик на проекте → Add → New Item → Class).
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Threading.Tasks;
namespace App17
{
public interface IAuthenticator
{
Task<AuthenticationResult> Authenticate(string authority, string resource, string clientId, string returnUri);
}
}
Как видишь, мы определяем сигнатуру асинхронного метода аутентификации. AuthenticationResult при успешной аутентификации будет содержать полученный от Azure Active Directory токен. Соберем проекты: правый клик на Solution → Build Solution.
Теперь создаем реализацию этого интерфейса для каждой из платформ. Он будет очень похож — основываться на вызове метода AcquireTokenAsync; единственное различие, почему его, собственно, и приходится создавать платформенно-зависимым, в специфике интеграции с платформенным UI через PlatformParameters для отображения всплывающих веб-диалогов аутентификации.
Android
Для Android нам нужно инициализировать PlatformParameters текущим окном/диалогом, то есть в терминах Android — Activity. Для этого мы можем использовать Forms.Context. Добавляем в Android-проект файл Helper.cs со следующим кодом.
using System;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Xamarin.Forms;
[assembly: Dependency(typeof(App17.Droid.Helper.Authenticator))]
namespace App17.Droid.Helper
{
class Authenticator : IAuthenticator
{
public async Task<AuthenticationResult> Authenticate(string authority, string resource, string clientId, string returnUri)
{
var authContext = new AuthenticationContext(authority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
var uri = new Uri(returnUri);
var platformParams = new PlatformParameters((Activity)Forms.Context);
var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
return authResult;
}
}
}
Если VS будет подчеркивать красным IAuthenticator — запусти сборку, чтобы проверить, что собирается успешно.
Также обрати особое внимание на метаатрибут Dependency для данного Helper namespace — именно он позволяет DependencyService сопоставить вызов из общего кода и реализацию для конкретной платформы.
[assembly: Dependency(typeof(App17.Droid.Helper.Authenticator))]
Также для Android нам нужно переопределить OnActivityResult метод в MainActivity.cs файле (внутри класса MainActivity) для правильной обработки и продолжения последовательности диалогов аутентификации (также добавляем два namespace через директивы using).
using Android.Content;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data);
}
Теперь переходим к iOS.
iOS
В iOS для PlatformParameters передаем UIViewController в качестве контекста, а именно RootViewController — то есть текущее окно.
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using UIKit;
using Xamarin.Forms;
[assembly: Dependency(typeof(App17.iOS.Helper.Authenticator))]
namespace App17.iOS.Helper
{
class Authenticator : IAuthenticator
{
public async Task<AuthenticationResult> Authenticate(string authority, string resource, string clientId, string returnUri)
{
var authContext = new AuthenticationContext(authority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
var controller = UIApplication.SharedApplication.KeyWindow.RootViewController;
var uri = new Uri(returnUri);
var platformParams = new PlatformParameters(controller);
var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
return authResult;
}
}
}
Не забываем про атрибут Dependency перед namespace. Для iOS не нужно переопределять метод возврата результатов окна/диалога.
Для того чтобы собрать iOS-проект, нам понадобится Mac-хост с агентом сборки или, например Mac-машина в облаке для сборки, которую предоставляет Microsoft App Center (опять-таки можно попробовать бесплатно — до 240 минут сборки в месяц плата не взимается).
Теперь реализация для Windows UWP (Universal Windows Platform).
Universal Windows Platform
Здесь тоже все очень просто — добавляем знакомый файл Helper.cs с реализацией интерфейса IAuthenticator, не забывая про атрибут Dependency.
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
[assembly: Dependency(typeof(App17.UWP.Helper.Authenticator))]
namespace App17.UWP.Helper
{
class Authenticator : IAuthenticator
{
public async Task<AuthenticationResult> Authenticate(string authority, string resource, string clientId, string returnUri)
{
var authContext = new AuthenticationContext(authority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
var uri = new Uri(returnUri);
var platformParams = new PlatformParameters(PromptBehavior.Auto, false);
var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
return authResult;
}
}
}
Теперь вернемся в общий проект для добавления вызова аутентификации.
Общий проект
Добавим кнопку для входа с использованием многофакторной аутентификации в общий проект. Заходим в общий проект Xamarin Forms, в котором содержится общий код и общий пользовательский интерфейс для всех платформ, и открываем через Solution Explorer главную страницу приложения — MainPage.xaml.
Если Solution Explorer не видно, его можно включить через меню View → Solution Explorer.
Обрати внимание, что VS предложит запустить визуализацию интерфейса через Live Player, — нажми Live Run, и ты сможешь вживую видеть на эмуляторе прямо во время редактирования, как изменяется пользовательский интерфейс приложения. Очень удобно для разработки. Визуализация будет идти с некоторой задержкой, поэтому просто продолжай редактирование.
В Xamarin Forms вся логика пишется на языке C#, а визуализация на языке разметки XAML.
INFO
Познакомиться подробно с разработкой на Xamarin Forms можно, скачав бесплатную и очень подробную книгу знаменитого Чарльза Петцольда (думаю, многие помнят этого автора) «Creating Mobile Apps with Xamarin.Forms».
В основном Page (окне) приложения MainPage.xaml добавим кнопку с обработчиком, для этого XAML-код для кнопки (Button) поместим вместе с существующим Label внутрь контейнера StackPanel. Этот простейший контейнер выстраивает внутри себя контролы в ряд по вертикали или горизонтали.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App17"
x:Class="App17.MainPage">
<StackLayout Orientation="Vertical">
<Label Text="Welcome to Xamarin.Forms!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"/>
<Button x:Name="btnLogin" Text="Login to Azure Active Directory" Clicked="btnLogin_Clicked" VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
В Xamarin Forms язык XAML поддерживает множество различных контейнеров, причем их можно вкладывать друг в друга, создавая гибко масштабируемые интерфейсы под любые размеры экранов.
После этого сразу перейдем в файл MainPage.xaml.cs (для этого нужно развернуть секцию с MainPage.xaml, чтобы увидеть код/code-behind) и добавим обработчик нажатия кнопки, а также нужные нам идентификаторы и namespaces.
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using Xamarin.Forms;
namespace App17
{
public partial class MainPage : ContentPage
{
public static string clientId = "<<INSERT YOUR CLIENT ID HERE>>";
public static string authority = "https://login.windows.net/common";
public static string returnUri = "http://MFATestPCL-redirect";
private const string graphResourceUri = "https://graph.windows.net";
private AuthenticationResult authResult = null;
public MainPage()
{
InitializeComponent();
}
private async void btnLogin_Clicked(object sender, EventArgs e)
{
try
{
var auth = DependencyService.Get<IAuthenticator>();
authResult = await auth.Authenticate(authority, graphResourceUri, clientId, returnUri);
var userName = authResult.UserInfo.GivenName + " " + authResult.UserInfo.FamilyName;
await DisplayAlert("Token", userName, "Ok", "Cancel");
}
catch (Exception ex)
{
await DisplayAlert("Error", ex.Message, "Ok");
}
}
}
}
После этого прописываем в приложение в код идентификаторы, которые мы сконфигурировали при регистрации приложения в облачном сервисе Azure Active Directory. Их полное соответствие в приложении и сервисе необходимо, чтобы сервис не отклонял обращения приложения:
clientID
— это Application ID из наших настроек облачного сервиса выше.
public static string clientId = "<<INSERT YOUR CLIENT ID HERE>>";
returnURI
— это Redirect URI из наших настроек облачного сервиса выше.
public static string returnUri = "http://MFATestPCL-redirect";
Все готово.
Запуск приложения
Теперь можно запускать приложение (например, на эмуляторе по F5) и аутентифицироваться учетными данными пользователя, которого мы завели выше. При первом входе после корректного ввода имени пользователя и пароля система предложит пользователю зарегистрировать свой мобильный номер для получения подтверждающих звонков или СМС — выбор будет зависеть от того, какие возможности многофакторной аутентификации мы разрешили в параметрах Service Settings настроек Multi-factor Authentication раздела облачного сервиса.
После регистрации мы получим звонок или СМС на мобильный телефон. Только после ввода с цифровой клавиатуры запрошенного подтверждения или СМС-кода мы успешно аутентифицируемся в системе.
При успешной аутентификации Azure Active Directory выдаст JWT-токен (JSON Web Token), который можно использовать для авторизации (разрешения) действий пользователя в мобильном приложении. Токен подписан цифровой подписью, подтверждающей его целостность, и содержит ряд полей, по которым мы можем определять не только то, что пользователь успешно прошел аутентификацию, но и к каким ролям он принадлежит. Для реализации высокой степени защиты и безопасности нужно дополнительно проверять сертификат, которым подписан токен, на то, что он действительный. Также токен нужно передавать только через защищенные соединения и хранить в защищенных хранилищах.
Если токен не получен — значит, пользователь аутентификацию не прошел и доступ к функциональности мобильного приложения разрешен быть не должен.
Полный пример приложения на GitHub содержит операцию выхода/logout, которая очищает кеш токенов в приложении, а также cookie, которые могут в соответствии с настройками сервиса Azure Active Directory позволять пользователю не вводить повторно пароль в течение настроенного времени.
Итак, если учетные данные верны и второй фактор подтвержден, мы успешно пройдем многофакторную аутентификацию и увидим данные из полученного токена подтвержденного пользователя.