Windows Identity Foundation ASP.NET MVC проектів


У цій статті хотілося б розповісти про те, як можна використовувати Windows Identity Foundation у своїх ASP.NET MVC проектах, і написати свій Identity Server, WIF платформі. По-перше, тому що, загальної інформації, в інтернеті, досить, а от коли справа стосується конкретики, тут виникають проблеми. Так як ідеологію і приватні випадки можна ще знайти, а от коли справа стосується конкретики, доводиться збирати по крихтах. І по друге, те, що зараз пропонує Microsoft, використовуючи надбудови над Visual Studio, не зовсім годиться, я б навіть сказав, що зовсім не годиться при розробці рішень, складніше домашньої сторінки або сайту — візитки. Крім усього іншого, я не дуже люблю, коли міфічний майстер настройки що зробив з солюшеном, і сказав що «начебто має працювати».

В якості прикладу, створимо, і налаштуємо, самий примітивний клієнт, який буде авторизуватися через найпримітивніший сервер авторизації (Identity Server) працює за WS-Federation протоколом.

Для того, що б визначитися, в яких випадках має сенс «городити город» зі своїм сервером авторизації, достатньо подивитися, як він працює. Отже, ми хочемо надавати клієнту кілька сервісів, наприклад кілька сайтів, наприклад з WCF сервісами, і припустимо REST API. Погодьтеся, що користувачеві буде досить дискомфортно окремо авторизуватися на кожному з наших сайтів, при переході з одного на інший. Тут досить проста ідея, яка полягає в тому, що користувачу, при авторизації на одному з сервісів (ресурсів), видається якийсь Token. А надалі, інший, або той самий, сервіс (ресурс) вже довіряє авторизованому користувачеві, на основі існування у клієнта цього самого сертифіката, ну і так далі…

Просто для наочності розуміння, наведу вподобане порівняння (на жаль посилання на знайшов). Наприклад людина отримує паспорт, після певної процедури перевірки, а в подальшому, надаючи свій паспорт, людині довіряють, на основі наявності у нього паспорта. У цьому порівнянні, паспорт людини, і є токен клієнта.

Відразу ж можна зробити висновок, що найчастіше, безглуздо видавати «паспорт», якщо він буде перевірятися тільки в одному місці.

Звичайно ж, у токен, як і у паспорти, є час життя, і точно так само, токен може бути пересоздан, на основі застарілого. І по аналогії, як і паспорт, токен може бути різним, тобто представляти із себе практично все що завгодно, або це заголовок реквеста, або base64 рядок, як наприклад JWT Token (JSON Web Token). А по факту, сам токен містить інформацію про себе (час коли він був створений останній раз, публічний ключ сертифіката тощо), а так само список клеймов, що містять інформацію про клієнта. Для опису сертифіката, ми будемо використовувати мову SAML (Security Assertion Markup Language).

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

var claimsList = new List<Claim>
{
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "Tester")
};

Існує цілий ряд, зареєстрованих неймспейсов, яких більш ніж вистачає для того що б описати профіль клієнта, від списку ролей, до аватарки. При бажанні ми можемо створювати свої простори імен, єдине про що потрібно пам'ятати, що сам об'єкт Claim, повинен сериализоваться.

Звідси можна зробити мабуть два основних висновки, перший — це що просто для авторизації користувача на сайті, використовувати Identity Server, просто невиправдано, і другий — що оскільки ми збираємося передавати токен, між різними ресурсами, нам знадобиться безпека транспорту, в першу чергу. І так приступимо, і почнемо мабуть з налаштування транспорту, оскільки якщо у нас не буде trusted транспорту, нічого працювати просто не зможе, а для цього, в першу чергу, нам знадобиться сертифікат.

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

Наприклад, ім'я домену, який ми будемо використовувати в тестових цілях буде identity.com. Спочатку для створення сертифіката скористаємося утилітою makecert.
makecert-r-n "CN=*.identity.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 identity.com.pvk identity.com.cer

В результаті ми отримаємо два файлу identity.com.pvk та identity.com.cer.Тут все гранично зрозуміло, так і інформації по утиліті makecert більш ніж достатньо. Єдиний момент, на якому хотілося б зупинитися докладніше, так це на CN на який ми видаємо сертифікат. Якщо видати сертифікат просто на identity.com, то нам, надалі, буде незручно локально моделювати ситуацію з розподіленими ресурсами, а от використання *, значно спрощує наше завдання тобто *.identity.com. Використання *, дає нам можливість локально створювати довільну кількість доменів виду «any name».identity.com.

Далі, для перевірки сертифіката видавця, скористаємося утилітою cert2spc.

cert2spc identity.com.cer identity.com.spc

І в результаті, отримаємо файл identity.com.spc, який нам знадобиться для утиліти pvk2pfx. За допомогою якої ми згенеруємо необхідний нам pfx файл.
pvk2pfx-pvk identity.com.pvk-spc identity.com.spc-pfx identity.com.pfx-pi "qwerty"

В результаті, ми отримали identity.com.pfx файл, що містить private ключ, з паролем qwerty. Залишилося зареєструвати IIS і в системі.

Налаштування HTTPS
Для того що б, наш сервер IIS почав працювати з нашим сертифікатом, по-перше, нам потрібно імпортувати наш pfx файл, в Trusted Root Certification Authorities зону через оснастку MMC, та виконати Import в самому сервері IIS, в розділі Server Certificates.

Тепер все готово до налаштування нашого клієнтського сайту. Для цього створимо новий сайт IIS, з ім'ям client.identity (втім, без різниці з яким), головне що б App Pool нашого сайту працював під .Net 4.0 (це якщо сайт під скомпільовано .Net 4.0, 4.5). І вказуємо Physical path, на директорію нашого клієнтського сайту.

Далі налаштовуємо наш HTTPS, в розділі Binding. Після вибору https в полі type, нам необхідно вибрати наш згенерований сертифікат, і тільки після цього нам стає доступним для редагування полі Host name. Якщо сертифікат ми згенерували з "*", то ми можемо вказувати практично будь-яке ім'я нашого сайту, головне сто би воно закінчувалося на identity.com, тобто ім'я нашого тестового домену. Надалі ми можемо змінити наші биндинги в розділі Bindings, нашого сайту. Залишився тільки останній «штрих», так це змінити hosts файл, по дорозі (c:\Windows\System32\drivers\etc\) і додати туди рядок з ім'ям биндинга нашого сайту:
127.0.0.1 client.identity.com

Все, можна перевіряти роботу локального https, просто за адресою: client.identity.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 можливість, при запуску, автоматично робити 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.
<authentication mode="Ні"/>

Далі реєструємо наші секцію, для Identity Model в розділі configSections:
<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" />

Додаємо і налаштовуємо ці секції, спочатку system.identityModel:
<system.identityModel>
<identityConfiguration saveBootstrapContext="true">
<audienceUris>
<add value="https://client.identity.com/" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.Configurationbasedissuernameregistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<trustedIssuers>
<add thumbprint="BDB8FF11361F312C06EDF1D20B05E775A123BE22" name="https://server.identity.com/issue/wsfed" />
</trustedIssuers>
</issuerNameRegistry>
<certificateValidation certificateValidationMode="Ні" />
</identityConfiguration>
</system.identityModel>

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

Тільки без пробілів. А поле name — це і є адреса нашого бедующего сервера, наприклад, в нашому випадку server.identity.com + /issue/wsfed. Використовувати саме wsfed не обов'язково, оссобенності в нашому випадку бедующего сервера — це може бути що завгодно. Просто wsfed — це скорочення від WS-Federation.
Далі додаємо секцію system.identityModel.services:
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="true" />
<wsFederation passiveRedirectEnabled="true" issuer="https://server.identity.com/issue/wsfed"
realm="https://client.identity.com/"
reply="https://client.identity.com/" requireHttps="true" />
</federationConfiguration>
</system.identityModel.services>

Тут все досить просто, iisuer — це наш сервер з секції вище, а reply — це адреса куди, в подальшому відповідати сервера.

Залишилося зареєструвати модулі, в розділі system.webServer.
<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>

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

Після запуску програми (F5), і спробі користувачем викликати наш метод, ми побачимо GET запит за адресою нашого бедующего сервера:
https://server.identity.com/issue/wsfed?wa=wsignin1.0&wtrealm=https%3a%2f%2fclient.identity.com%2f&wctx=rm%3d0%26id%3dpassive%26ru%3d%252fAccount&wct=2014-09-23T13%3a36%3a21Z&wreply=https%3a%2f%2fclient.identity.com%2f

Це запит на авторизацію від клієнта до сервера, за адресою server.identity.com/issue/wsfed.

Описаного вище, цілком вистачає, для мінімальної налаштування клієнтського додатка, що працює з 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.identity.com, використовуючи все той же, згенерований нами сертифікат.

Створимо SignIn View для SignIn методу контролера Account, і додамо в web.config запис:
<authentication mode="Forms">
<forms loginUrl="~/Account/signin" timeout="2880" />
</authentication>

І додамо в роуты, запис для методу контролера, який буде виконуватися при зверненні по шляху issue/wsfed.
routes.MapRoute("wsfederation",
"issue/wsfed",
new { controller = "WSFederation", action = "issue" });

Саме по цьому шляху звертається наш клієнт <>/issue/wsfed. Якщо ми контролер, пометим атрибутом Authorizе, то клієнтське додаток, при спробі виконати вхід в систему, буде потрапляти спочатку на метод SignIn, контролера Account, який в свою чергу буде повертати View, з логін формою нашого сервера.

Далі користувач вводить дані, необхідні для входу (наприклад логін пароль), і потрапляє на метод Issue. Зверніть увагу, що RequestString при цьому не змінюється, а залишається таким же, з яким звернувся клієнтське додаток.

Для того що б наш сервер розумів, що ж саме клієнт від нього хоче, розберемо аргументи запиту:
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");
}

Відповідно метод WSFederationMessage.CreateFromUri, повертає інстанцію спадкоємців абстрактного класу WSFederationMessage. Далі виконуємо дії, або входу в систему, або виходу.

При виконанні входу в систему за WS-Federation протоколу, виконуємо статичний метод:
FederatedPassiveSecurityTokenServiceoperations.processsigninrequest

Цей метод, на основі отриманої інстанції класу SignInRequestMessage, і списку клеймов (Claims), сформує якийсь об'єкт RequestSecurityToken, який по суті і є нашим токеном клієнта, і віддасть його на метод GetScope нашого STS сервісу. Для створення нашого STS сервісу, унаследуемся від абстрактного класу SecurityTokenService:
public class TokenService : SecurityTokenService

і перекриємо метод GetScope:
protected override Scope GetScope(ClaimsPrincipal principal, RequestSecurityToken request)

саме в цьому методі буде відбуватися аналіз або заповнення об'єкта RequestSecurityToken.Т.е. безпосередньо саме формування і перевірка сертифіката клієнта. Всю перевірку я просто не бачу сенсу описувати, так як простіше всього за методом пройтися дебагом, так як в методі немає нічого не тривіального.

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

Якщо буде цікаво, як це все працює, я «склеїв» спрощений клієнт і сервер, на з github.
Це просто полегшена версія сервера https://github.com/thinktecture/Thinktecture.IdentityServer.v2, зібрана поки що, на даний момент, в цілях демонстрації і не більше того, і звичайно не витримує ніякої критики.

висновок
Що хотілося б, особисто мені отримати в результаті, так це повноцінний Identity Server, який винесена вся робота з профілем користувача тобто логін, реєстрація, соцмережі, перегляд профілю і т.д. Відповідно з належним рівнем security. Але по факту, що б сам сервер можна було б підключити до розроблюваним ресурсу, і кожен раз не витрачати на систему авторизації. Ну і звичайно ж, хотілося б, інтегрувати роботу сервера, з WCF і REST сервісами, з розподілом доступу до методів за ролями клієнта. Але це поки тільки в планах.

Корисні посилання
Що таке Windows Identity Foundation: http://msdn.microsoft.com/ru-ru/library/ee748475.aspx
Исходники декількох Identity серверів, і корисних бібліотек: https://github.com/thinktecture
Хороший доповідь про Claims-based авторизації: https://www.youtube.com/watch?v=WHSDIiwQlS8
Ще приклади: http://claimsid.codeplex.com
І кодепроджект: http://www.codeproject.com/Articles/504399/Understanding-Windows-Identity-Foundation-WIF

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.