Windows Identity Foundation — для ASP’шных MVC проектов

Хотелось бы рассказать о том, как можно использовать Identity Foundation в своих ASP.NET MVC проектах, и написать свой Identity Server, на WIF платформе. Потому что, общей информации, в тырнете, достаточно, а вот когда дело касается конкретики, возникают проблемы.

Так как идеологию и частные случаи можно найти, а когда дело касается конкретики, приходится собирать по крупицам. И во вторых, то что предлагает Microsoft, используя надстройки Visual Studio, не совсем годится, я бы даже сказал, совсем не годится при разработке решений, сложнее домашней странички или сайта — визитки(просто у меня слабая степень аутизма). Кроме всего прочего, я не очень люблю, когда мифический мастер настройки что сделал с проектом, и сказал что «чувак, должно работать».

В качестве примера, создам, и настрою, самый примитивный клиент, который будет авторизоваться через самый примитивный сервер авторизации работающий на WF-Federation протоколе.

Для того, что бы определиться, в каких случаях имеет смысл «городить огород» со своим сервером авторизации, достаточно посмотреть как он работает. Мы хотим предоставлять клиенту несколько сервисов, например несколько сайтов, например с WCF сервисами, и REST API. Согласитесь, что пользователю будет достаточно дискомфортно отдельно авторизоваться на каждом из наших сайтов, при переходе с одного на другой. Тут достаточно простая идея, которая заключается в том, что пользователю, при авторизации на одном из сервисов, выдаётся некий Token. А в дальнейшем, другой, или тот же, сервис уже доверяет авторизированному пользователю, на основе существования у клиента этого самого токена, ну как-то так.

Для наглядности понимания, приведу сравнение. Например человек получает паспорт, после некой процедуры проверки, а в дальнейшем, предоставляя свой паспорт, человеку доверяют, на основе наличия паспорта. В данном сравнении, паспорт человека, и есть токен клиента.

Сразу же можно сделать вывод, что зачастую, бессмысленно выдавать «паспорт», если он будет проверяться только в одном месте.

Конечно же, у токена, как и у паспорта, есть время жизни, и точно так же, токен может быть пересоздан, на основе устаревшего. И по аналогии, как и паспорт, токен может быть разным, т.е. представлять из себя практически всё что угодно, или это заголовок реквеста, или base64 строка, как например JWT Token (JSON Web Token). А по факту, сам токен содержит информацию о себе (время последнего создания, публичный ключ сертификата, и т.д.), а так же список клеймов, содержащих информацию о клиенте. Для описания токена, мы будем использовать язык SAML (Security Assertion Markup Language).

Ещё одно важное понятие, — это клеймы (Claims). Клеймы входят в состав нашего токена, и несут информацию о клиенте в целом. Фактически — это словарь, состоящий из пары Key/Value, в котором Key — namespace описывающий тип поля Value, а само по себе поле Value — это простая строка. В .NET это представлено типизированным списком:

[c language=»#»]
var claimsList = new List
{
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "Tester")
};
[/c]

Существует целый ряд зарегистрированных неймспейсов, которых более чем хватает для того что бы описать профиль клиента, от списка ролей, до авы. При желании мы можем создавать свои пространства имён, единственное о чём нужно помнить, что объект Claim должен сериализоваться.

Отсюда можно сделать два основных вывода, первый — это что для просто авторизации пользователя, на сайте, использовать Identity Server, просто неоправданно, и второй — поскольку мы собираемся передавать токен, между различными ресурсами, нам понадобится безопасность транспорта, в первую очередь. Приступим, и начнём с настройки транспорта, поскольку если у нас не будет trusted транспорта, ничего работать просто не сможет, а для этого, в первую очередь, нам понадобится сертификат.

Создание сертификата

Вот с этого момента, у многих начинаются вопросы, по поводу создания сертификата, настройки HTTPS на сервере, и так далее. Для работы и отладки, нам понадобится сам IIS установленный локально, простое ASP.NET MVC приложение, которое будет нашим сайтом, и доверенный сертификат. Нам нужен не просто сертификат, а сертификат выданный на какое либо доменное имя, покупать его, для тестовых целей — экономически не выгодно, поэтому мы сделаем его сами.

Например, имя домена, который мы будем использовать в тестовых целях будет polygon.com. Сначала, для создания сертификата воспользуемся утилитой makecert.

makecert -r -n "CN=*.polygon.com" -cy authority -b 01/01/2000 -e 01/01/2099 -a sha1 -sr localMachine -sky Exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -sv polygon.com.pvk polygon.com.cer

В результате выполнения мы получим два файла polygon.com.pvk и polygon.com.cer.Тут все предельно понятно, да и информации по утилите makecert более чем достаточно. Единственный момент, на котором хотелось бы остановиться по подробнее, так это на CN на который мы выдаём сертификат. Если выдать сертификат просто на polygon.com, то нам, в дальнейшем, будет неудобно локально моделировать ситуацию с распределёнными ресурсами, а вот использование *, значительно упрощает нашу задачу т.е. *.polygon.com. Использование *, даёт нам возможность локально создавать произвольное количество доменов вида «any name».client.com.

Далее, для проверки сертификата издателя, воспользуемся утилитой cert2spc

cert2spc polygon.com.cer polygon.com.spc

И в результате, получим файл polygon.com.spc, который нам понадобится для утилиты pvk2pfx. С помощью которой мы сгенерируем необходимый нам pfx файл.

pvk2pfx -pvk polygon.com.pvk -spc polygon.com.spc -pfx polygon.com.pfx -pi "qwerty"

Настройка HTTPS

Для того что бы, наш IIS сервер начал работать с нашим сертификатом, во первых, нам нужно импортировать наш pfx файл, в Trusted Root Certification Authorities зону через оснастку MMC, и выполнить Import в самом IIS сервере, в разделе Server Certificates.

Теперь всё готово к настройке нашего клиентского сайта. Для этого создадим новый сайт в IIS, с именем client.polygon (в прочем, без разницы с каким), главное что бы App Pool нашего сайта работал под .Net 4.0 (это если сайт скомпилирован под .Net 4.0, 4.5). И указываем Physical path, на директорию нашего клиентского сайта.

Далее, настраиваем наш HTTPS, в разделе Binding. После выбора https в поле type, нам необходимо выбрать наш сгенерированный сертификат, и только после этого нам становится доступно для редактирования поле Host name. Если сертификат мы сгенерировали с «*«, то мы можем указывать практически любое имя нашего сайта, главное сто бы оно заканчивалось на polygon.com, т.е. имя нашего тестового домена. В дальнейшем мы можем изменить наши биндинги в разделе Bindings, нашего сайта. Остался только последний «штрих», так это изменить hosts файл, по пути (c:\Windows\System32\drivers\etc\) и добавить туда строку с именем биндинга нашего сайта:

127.0.0.1 client.identity.com

Всё, можно проверять работу локального https, просто по адресу: client.polygon.com.

В результате, мы должны увидеть наше web приложение, а в адресной строке, то что наш сертификат, является доверенным, т.е. никаких предупреждений браузера.

Если Вы скачаете IdentityTrainingKitVS2010 с сайта Microsoft, то можно немного облегчить себе жизнь, выполнив SetupCertificates.cmd, например по пути IdentityTrainingKitVS2010\Labs\MembershipAndFederation\Source\Setup\Scripts\SetupCertificates.cmd

Этот скрипт сделает практически тоже самое, только для уже готового сертификата из примеров localhost.pfx (у него пароль xyz). Соответственно обращение к сайту (например Default Web Site), будут работать через localhost, а все ваши приложения которые должны работать через https, должны быть созданы ни как, Web Site, а как Web Application, под localhost сайтом.

Настройка клиента

Теперь надо произвести некоторую настройку нашего клиентского приложения. Для начала, в настройках проекта, установим адрес нашего сайта для поля Custom Web Server(В Visual Studio это делается в настройках проекта)

Это даст Visual Studio возможность, при запуске, автоматически делать Attach to Process к w3wp.exe процессу (процесс сайта IIS).

Теперь, нам надо разобраться с референсами нашего сайта, и добавить две сборки из GAC, System.IdentityModel.dll и System.IdentityModel.Services.dll. А так же удалить лишнее, то что нам будет мешать — это NUget пакеты DotNetOpenAuth, они нам не понадобятся, а будут только мешать, а для этого, необходимо удалить пакет Microsoft.AspNet.WebPages.OAuth. Если, по каким либо причинам, Вы не хотите их трогать, то как вариант, — это настройка регистрации в web.config.

И последним шагом, в настройке клиентского приложения — это настройка самого web.config. Во первых, в разделе sysytem.web, установить метод authentication в none.

[xml]
<authentication mode="None"/>
[/xml]

Регистрируем наши секции, для Identity Model в разделе configSections:

[xml]
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
[/xml]

Для начала, в секции audienceUris, добавляем адрес нашего клиенского сайта, далее добавляем запись в секциию trustedIssuers, где thumbprint это значение Thumbprint из нашего сгенерированного файла polygon.crt, или другого сертификата транспорта, по которому будет работать сервер.

Только без пробелов. А поле name — это и есть адрес нашего бедующего сервера, например, в нашем случае server.polygon.com + /issue/wsfed. Использовать именно wsfed не обязательно, оссобенно в нашем случае бедующего сервера — это может быть что угодно. Просто wsfed — это сокращение от WS-Federation.
Далее добавляем секцию system.identityModel.services:

[xml]
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="true" />
<wsFederation passiveRedirectEnabled="true" issuer="https://server.polygon.com/issue/wsfed"
realm="https://client.polygon.com/"
reply="https://client.polygon.com/" requireHttps="true" />
</federationConfiguration>
</system.identityModel.services>
[/xml]

Тут всё просто, issuer — это наш сервер из секции выше, а reply — это адрес куда, в дальнейшем отвечать серверу.

Осталось зарегистрировать модули, в разделе system.webserver.

[xml]
<modules>
<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>
[/xml]

Можно проверять работу нашего клиента, достаточно попробовать с View вызвать любой метод или метод контроллера помеченного атрибутом Authorize.

После запуска приложения, и попытке пользователем вызвать наш метод, мы увидим GET запрос по адресу нашего бедующего сервера:

https://server.polygon.com/issue/wsfed?wa=wsignin1.0&wtrealm=https%3a%2f%2fclient.polygon.com%2f&wctx=rm%3d0%26id%3dpassive%26ru%3d%252fAccount&wct=2014-09-23T13%3a36%3a21Z&wreply=https%3a%2f%2fclient.polygon.com%2f

Это запрос на авторизацию, от клиента серверу.

Описанного выше, вполне хватает, для минимальной настройки клиентского приложения, работающего с WIF Identity Server, даже если Вы используете сервер сторонних разработчиков, главное что бы сервер поддерживал WS-Federation.

Создание сервера

Перед началом создания Identity сервера, надо обязательно упомянуть о такой вещи как STS (Security Token Service). Фактически — это сервис, и не важно на каком языке и платформе он написан. А наш ASP.NET MVC Identity Server — это по сути UI. Для создания своего STS, поскольку мы всё таки используем .NET, то нам будет удобно воспользоваться уже теми инструментами которые есть в платформе.

Наш Identity Server, так же как и клиент, по сути представляет собой, ASP.NET MVC приложение, для которого тоже необходимо настроить HTTPS, и, в нашем случае, назначим ему биндинг server.polygon.com используя все тот же, сгенерированный нами сертификат.

Создадим SignIn View для SignIn метода контроллера Account, и добавим в web.config запись:

[xml]
<authentication mode="Forms">
<forms loginUrl="~/Account/signin" timeout="2880" />
</authentication>
[/xml]

И добавим в роуты, запись для метода контроллера, который будет выполняться при обращении по пути issue/wsfed.

[c language=»#»]

routes.MapRoute("wsfederation",
"issue/wsfed",
new { controller = "WSFederation", action = "issue" });

[/c]

Именно по этому пути обращается наш клиент <>/issue/wsfed. Если мы контроллер, пометим атрибутом Authorize, то клиентское приложение, при попытке выполнить вход, в систему, будет попадать сначала на метод SignIn, контроллера Account, который в свою очередь будет возвращать View, с логин формой нашего сервера.

Далее пользователь вводит данные, необходимые для входа (например логин пароль), и попадает на метод Issue. Обратите внимание, что RequestString при этом не меняется, а остаётся таким же, с которым обратилось клиентское приложение.

Для того что бы наш сервер понимал, что же именно клиент от него хочет, разберём аргументы запроса:

[c language=»#»]

public ActionResult Issue()
{
WSFederationMessage message = WSFederationMessage.CreateFromUri(HttpContext.Request.Url);

// Sign in
var signinMessage = message as SignInRequestMessage;
if (signinMessage != null)
{
return ProcessWSFederationSignIn(signinMessage);
}

// Sign out
var signoutMessage = message as SignOutRequestMessage;
if (signoutMessage != null)
{
return ProcessWSFederationSignOut(signoutMessage);
}

return View("Error");
}

[/c]

Соответственно метод WSFederationMessage.CreateFromUri, возвращает инстанцию наследников абстрактного класса WSFederationMessage. Далее выполняем действия, или входа в систему, или выхода.

При выполнении входа в систему по WS-Federation протоколу, выполняем статический метод:

[c language=»#»]
FederatedPassiveSecurityTokenServiceOperations.ProcessSignInRequest
[/c]

Этот метод, на основе полученной инстанции класса SignInRequestMessage, и списка клеймов (Claims), сформирует некий объект RequestSecurityToken, который по сути и является нашим токеном клиента, и отдаст его на метод GetScope нашего STS сервиса. Для создания нашего STS сервиса, унаследуемся от абстрактного класса SecurityTokenService:

[c language=»#»]
public class TokenService : SecurityTokenService
[/c]

Перекроем метод GetScope:

[c language=»#»]
protected override Scope GetScope(ClaimsPrincipal principal, RequestSecurityToken request)
[/c]

в этом методе будет происходить анализ или заполнение объекта RequestSecurityToken. Т.е. само формирование, и проверка токена клиента. Всю проверку я просто не вижу смысла описывать, так как проще всего по методу пройтись дебагом, так как в методе нет ничего не тривиального.

В принципе, вышеописанного в самом своём минимуме достаточно, для того что бы получить примитивный Identity Server, через который клиент может авторизоваться.

В заключении

Что хотелось бы, лично мне получить в результате, так это полноценный Identity Server, в который вынесена вся работа с профилем пользователя т.е. логин, регистрация, соцсети, просмотр своего профиля и т.д. Соответственно с должным уровнем security. Но по факту, что бы сам сервер можно было бы подключить к разрабатываемому ресурсу, и каждый раз не тратить на время на систему авторизации. Ну и конечно же, хотелось бы, интегрировать работу сервера, с WCF и REST сервисами, с распределением доступа к методам по ролям клиента. Но я же ленивец.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *