Сервисом для осуществления сценариев аутентификации в Windows Azure является Windows Azure Active Directory. Этот сервис не является полным аналогом локального Active Directory, скорее он расширяет локальный каталог в облако, его «зеркалом».
Windows Azure Active Directory состоит из трех основных компонентов: REST-сервиса, с помощью которого можно создавать, получать, обновлять и удалять информацию из каталога, а также использовать SSO (в случае интеграции с Office 365, Dynamics, Windows Intune, например); интеграции с различными провайдерами идентификации типа Facebook и Google, а также библиотеки, упрощающей доступ к функциональности Windows Azure Active Directory.
Сейчас Windows Azure Active Directory предоставляет удобный доступ к следующей информации:
Пользователи: пароли, политики безопасности, роли.
Группы: Security/Distribution-группы.
И другой основной информации (например, о сервисах). Всё это предоставляется с помощью Windows Azure AD Graph — социального корпоративного графа с интерфейсом, поддерживающим REST, с представлением проводника для легкого обнаружения информации и связей.
Когда вы хотите интегрироваться с локальной инфраструктурой под управлением AD, вам необходимо установить и настроить Active Directory Federation Services Version 2. WAAD имеет полную поддержку ADFS.
Давайте посмотрим на примере, как использовать WAAD для настройки SSO в ASP.NET приложении. Ниже мы создадим новый тенант WAAD на Windows Azure, интегрируем тенант с MVC-приложением, настроим SSO и воспользуемся Graph AD API.
Что нужно для того, чтобы повторить демо:
· Подписка Windows Azure - хватит триала: free trial
Создадим тенант WAAD на Windows Azure. Зайдем на https://manage.windowsazure.com/ и выберем Active Directory справа. Нажмем ADD.
Название каталога должно быть уникальным, так как является публичным. Регион - Россия. Регион должен соответствовать не тому, где находитесь вы, а тому, в котором будет находиться организация, так как именно в этом регионе, с его юрисдикцией и прочим будет создан тенант.
После создания тенанта он будет настроен для сохранения данных о пользователях в облаке. Можно WAAD синхронизировать и с локальной AD, но это задача исключительно инфраструктурная. Если перейти на вкладку Пользователи уже внутри тенанта, то мы увидим, что туда был автоматически добавлен пользователь-создатель.
Добавим еще одного пользователя.
Добавление пользователя - процесс, состоящий из трех шагов: типа учетной записи пользователя...
... Его роли в организации и тенанте...
... и установке временного пароля.
Создадим новый пароль и завершим настройку нового пользователя. Под этим пользователем и будем заходить в систему позже.
Теперь зарегистрируем MVC-приложение в тенанте WAAD. Создадим ASP.NET MVC 4 приложение типа Intranet. В Properties проекта установим SSL Enabled в True. Скопируем SSL URL.
Это нужно для того, чтобы обезопасить передачу контента, поскольку HTTP не обеспечит должного уровня для сохранения, например, токенов безопасности.
Установим использование Local IIS Web Server как сервер в настройках проекта. Вставим скопированный SSL URL в поле Project URL.
Добавим наше приложение в тенант на портале. На портале нужно перейти во вкладку APPLICATIONS внутри тенанта и нажать Добавить
Приложение должно быть типа "разрабатываемое моей организацией".
На первом шаге добавления приложения нужно ввести его название, оставив тип таким же (Веб-приложение).
На втором шаге нужно вставить в URL-АДРЕС ВХОДА и URI КОДА ПРИЛОЖЕНИЯ скопированный в VS SSL URL.
URL-адрес входа - это адрес вашего приложения, используется внутри WAAD для редиректов пользователя с WAAD обратно на ваше приложение.
URIкода приложения - это идентификатор приложения, который должен быть обязательным внутри тенанта, так как используется для служебных целей WAAD.
Все, приложение зарегистрировано. Теперь скопируем URL-АДРЕС ДОКУМЕНТА МЕТАДАННЫХ ФЕДЕРАЦИИ из раздела РАЗРЕШИТЬ ПОЛЬЗОВАТЕЛЯМ ВХОД.
Этот URL нужно для подключения приложения к WAAD. То есть сейчас мы сделали так, что сторона сервера знает о приложении, но еще не сделали так, чтобы приложение знало о сервере. Здесь нам поможет инструмент VS 2012, называющийся Identity And Access. Кстати, этот инструмент не появится для проекта до тех пор, пока проект не будет ориентирован на использование .NET Framework 4.5.
Вызовем Identity And Access.
Дальше нам необходимо очень аккуратно произвести настройку нескольких параметров. На шаге Providers выбираем Use a business identity provider (тут можно использовать и другие типы, но нужные настройки будут только при выборе второй опции) и вставляем скопированный с портала URL. Этот URL ведет на файл с метаданными федерации, что позволяет "подружить" приложение со спецификацией и метаданными нашего тенанта.
Нажмем OK. Произошло изменение web.config - теперь приложение знает, куда обращаться за процессом аутентификации пользователей.
Теперь немного попрограммируем. Откроем HomeController.cs и заменим метод Index на нижеуказанный код. Потребуется разрезолвить некоторые зависимости.
public ActionResult Index()
{
ClaimsPrincipal cp = ClaimsPrincipal.Current;
string fullname = string.Format("{0} {1}", cp.FindFirst(ClaimTypes.GivenName).Value,
cp.FindFirst(ClaimTypes.Surname).Value);
ViewBag.Message = string.Format("Dear {0}, welcome to the Expense Note App", fullname);
return View();
}
ClaimsPrincipal в объектной модели .NET 4.5 - это любая сущность, которая хочет (или уже) аутентифицироваться в конечной системе.
В Global.asax в обработку события Application_Start добавим следующую строку:
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
Запустим приложение и согласимся с тем, что есть проблема с сертификатом. Вся проблема заключается в том, что сертификат самоподписанный. Будет полноценный сертификат, проверенный центром - и ошибки не возникнет.
Произошел редирект на WAAD. Залогинимся в систему под пользователем, которого создали ранее в тенанте.
Мы вошли в систему. Обратите внимание, что приложение автоматом скоммуницировало с каталогом тенанта и получило информацию о пользователе.
Хорошо, когда есть возможность залогиниться. Раньше были некоторые вопросы, связанные с тем, как сделать кнопку Sign Out и правильно разлогинить пользователя. Давайте сделаем это, создав контроллер SignOutController и добавим код, приведенный ниже. Зависимости сразу не разрезолвятся - в референсы проекта надо будет добавить сборку System.Identitymodel.Services.
Код:
public ActionResult Index()
{
return View("SignOut");
}
public void SignOut()
{
WsFederationConfiguration fc =
FederatedAuthentication.FederationConfiguration.WsFederationConfiguration;
string request = System.Web.HttpContext.Current.Request.Url.ToString();
string wreply = request.Substring(0, request.Length - 7);
SignOutRequestMessage soMessage =
new SignOutRequestMessage(new Uri(fc.Issuer), wreply);
soMessage.SetParameter("wtrealm", fc.Realm);
FederatedAuthentication.SessionAuthenticationModule.SignOut();
Response.Redirect(soMessage.WriteQueryString());
}
}
Добавим в папку Views папку SignOut и представление для SignOut. Внутри можем написать что угодно - это будет страница, появляющаяся после окончания выхода из системы.
Добавим кнопку для Sign Out в общую страницу Layout.cshtml.
<section id="login">
@if (Request.IsAuthenticated)
{
<text>Ты внутри системы, <span class="username">@User.Identity.Name</span>!
@Html.ActionLink("Signout","SignOut", "SignOut")</text>
}
else {
<text> Ты вне системы. </text>
}
</section>
Запустим проект и протестируем то, что получилось:
Нас перебросит на аутентификацию WAAD. Если вас устраивает такое поведение - ничего страшного. Если хочется все-таки видеть представление, созданное выше, то надо добавить немного кода в web.config:
<configuration>
...
<location path="FederationMetadata">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
<location path="SignOut">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
</configuration>
Вот и все - мы использовали WAAD для того, чтобы аутентифицировать простого пользователя в системе. В процессе мы также немного попрограммировали, чтобы приложение получало из запрошенного токена безопасности от WAAD данные о пользователе.
Давайте теперь посмотрим на Graph API. Скачаем вспомогательную библиотеку по ссылке http://go.microsoft.com/fwlink/?LinkID=290812и добавим ее как существующий проект в наш проект.
В web.config основного проекта добавим две настройки с соответствующими значениями (эти значения берутся со вкладки настройки настроенного приложения в тенанте WAAD):
<appSettings>
<add key="ClientId" value="[CLIENT-ID]"/>
<add key="Password" value="[APPLICATION-KEY]"/>
...
<appSettings>
Теперь нужно обновить библиотеки в основном проекте:
- Microsoft.Data.Edm,
- Microsoft.Data.OData
- System.Spatial
Эти библиотеки нужно обновить с 5.2.0. Сначала удалим их из References, затем, загрузив новую версию по ссылке http://www.microsoft.com/en-us/download/details.aspx?id=36516, подключим заново.
Добавим в основной проект проект GraphHelper, добавленный ранее в решение.
Все, теперь мы можем поспрашивать наш тенант с помощью Graph API. Добавим немного кода в HomeController:
public ActionResult Users()
{
//get the tenantName
string tenantName = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
// retrieve the clientId and password values from the Web.config file. Accomplished in previous step.
string clientId = ConfigurationManager.AppSettings["ClientId"];
string password = ConfigurationManager.AppSettings["Password"];
// get a token using the helper
AADJWTToken token = DirectoryDataServiceAuthorizationHelper.GetAuthorizationToken(tenantName, clientId, password);
// initialize a graphService instance using the token acquired from previous step
DirectoryDataService graphService = new DirectoryDataService(tenantName, token);
// get Users
//
var users = graphService.users;
QueryOperationResponse<User> response;
response = users.Execute() as QueryOperationResponse<User>;
List<User> userList = response.ToList();
ViewBag.userList = userList;
// For subsequent Graph Calls, the existing token should be used.
// The following checks to see if the existing token is expired or about to expire in 2 mins
// if true, then get a new token and refresh the graphService
//
int tokenMins = 2;
if (token.IsExpired || token.WillExpireIn(tokenMins))
{
AADJWTToken newToken = DirectoryDataServiceAuthorizationHelper.GetAuthorizationToken(tenantName, clientId, password);
token = newToken;
graphService = new DirectoryDataService(tenantName, token);
}
// get tenant information
//
var tenant = graphService.tenantDetails;
QueryOperationResponse<TenantDetail> responseTenantQuery;
responseTenantQuery = tenant.Execute() as QueryOperationResponse<TenantDetail>;
List<TenantDetail> tenantInfo = responseTenantQuery.ToList();
ViewBag.OtherMessage = "User List from tenant: " + tenantInfo[0].displayName;
return View(userList);
}
}
//Добавим еще одно представление в папку Home, назвав его Users. Добавим в него код:
@model IEnumerable<Microsoft.WindowsAzure.ActiveDirectory.User>
@{
ViewBag.Title = "Users";
}
<h1>@ViewBag.Message</h1>
<h2>@ViewBag.OtherMessage</h2>
<table>
<tr>
<th>
DisplayName
</th>
<th>
UPN
</th>
<th></th>
</tr>
@if (User.Identity.IsAuthenticated)
{
foreach (var user in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => user.displayName)
</td>
<td>
@Html.DisplayFor(modelItem => user.userPrincipalName)
</td>
</tr>
}
}
</table>
В _Layout.cshtml добавим еще один пункт в меню:
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("Users", "Users", "Home")</li>
</ul>
</nav>
Запустим проект и перейдем на вкладку Users.
Если возникает проблема с Insufficient privilegies, то действуем по инструкции, приведенной здесь: http://code.msdn.microsoft.com/windowsazure/Write-Sample-App-for-79e55502/view/Discussions
Резюме
Мы посмотрели, каким образом использовать Windows Azure Active Directory для обеспечения аутентификации пользователя и получении данных о нем из каталога в облаке. Возможности WAAD гораздо шире - например, все то же самое можно делать, синхронизировав каталог в облаке с локальным каталогом. Еще мы посмотрели, как работает Graph AD API.