Цель федеративной защиты — предоставить механизм для установления доверительных отношений между доменами, чтобы каждый пользователь мог аутентифицироваться в своем домене и в то же время получать доступ к приложениям и сервисам, принадлежащим к другому домену. Это позволяет реализовать такие процедуры, как единый вход (single sign-on), которые исключают необходимость в поддержке и управлении дубликатными учетными записями для доступа пользователей к разным приложениям и доменам и существенно снижают расходы на распространение сферы действия приложений для доверяемых сторон.
В модели федеративной защиты провайдер идентификаций (Identity Provider, IdP) выполняет аутентификацию и предоставляет сервис Security Token Service (STS), который выдает маркеры защиты. Эти маркеры в свою очередь несут информацию об аутентифицированном пользователе: его идентификацию и, возможно, другие сведения, в том числе о ролях и более детализированные данные о правах доступа. Эта информация называется заявками (claims), и управление доступом на основе заявок занимает центральное место в модели федеративной защиты. В этой модели приложения и сервисы авторизуют доступ к своим ресурсам и функциональности, основываясь на заявках от доверяемых издателей (STS).
Подсистемы наподобие Windows Identity Foundation (WIF) намного упрощают поддержку этого типа федерации идентификаций. WIF является инфраструктурой модели идентификаций для создания приложений и сервисов, ориентированных на заявки, и для поддержки вариантов федерации на основе SOAP (активная федерация) и браузера (пассивная федерация). В статье «Claims-Based Authorization with WIF» в ноябрьском номере «MSDN Magazine» за 2009 г. я уделила основное внимание применению WIF совместно с Windows Communication Foundation (WCF). В той статье я описывала, как реализовать основанные на заявках модели защиты для WCF-сервисов и как перейти на использование федерации идентификаций.
В этой статье я сосредоточусь на описании пассивной федерации. Я поясню потоки коммуникаций при использовании пассивной федерации, покажу несколько способов поддержки федерации в ваших приложениях ASP.NET, расскажу о методах авторизации на основе заявок в ASP.NET и рассмотрю сценарии применения в случаях с единым входом и единым выходом (single sign-out). Попутно мы обсудим нижележащие средства и компоненты WIF, обеспечивающие поддержку пассивной федерации.
Основы пассивной федерации
Все случаи пассивной федерации основаны на спецификации WS-Federation. Она описывает, как запрашивать маркеры защиты и как публиковать и получать документы метаданных федерации, которые упрощают установление доверительных отношений. Кроме того, WS-Federation описывает процедуры единого входа и выхода, а также другие концепции реализации федерации.
В WS-Federation излагается много деталей федерации, и в ней есть разделы, посвященные федерации на основе браузера, которая опирается на HTTP-команды GET и POST, перенаправления браузера и cookie.
Некоторые аспекты обмена сообщениями в рамках пассивной федерации тесно связаны со спецификацией WS-Trust. Например, в пассивной федерации применяется совместимая с браузером разновидность Request Security Token (RST) и RST Response (RSTR), когда от STS запрашивается маркер защиты. В пассивной федерации я буду называть RST сообщением запроса входа (sign-in request message), а RSTR — сообщением ответа на вход (sign-in response message). Однако основное внимание в спецификации WS-Trust уделяется активной федерации (на основе SOAP), например образуемой между Windows-клиентами и WCF-сервисами.
Простой случай пассивной федерации представлен на рис. 1.
Увеличить
Рис. 1. Простой случай пассивной федерации
Пользователи аутентифицируются в своем домене и получают доступ к веб-приложению согласно своим ролям. В этой схеме аутентификации участвуют пользователь (субъект), веб-браузер (запрашивающий), приложение ASP.NET [доверяющая сторона (relying party, RP)], IdP, отвечающий за аутентификацию пользователей в их домене, и STS, принадлежащий домену пользователя (IP-STS). Последовательность перенаправлений браузера гарантирует, что пользователь аутентифицируется в своем домене до обращения к RP.
Пользователь переходит с помощью браузера к RP-приложению (1) и перенаправляется к своему IdP для аутентификации (2). Если пользователь еще не аутентифицирован в IdP, IP-STS перенаправляет его на страницу входа для получения удостоверений (3). Пользователь предоставляет свои удостоверения (4) и аутентифицируется IP-STS (5). В этот момент IP-STS выдает маркер защиты согласно запросу на вход, и ответ на запрос входа, содержащий маркер, посылается RP через перенаправление браузера (6). RP обрабатывает маркер защиты и авторизует доступ на основе заявок в маркере (7). Если авторизация выполняется успешно, пользователю предоставляется изначально запрошенная им страница и возвращается cookie сеанса (8).
Реализация этого случая пассивной федерации на основе WIF и ASP.NET требует всего нескольких этапов.
- Установить доверительные отношения между RP и IdP (IP-STS).
- Включить поддержку пассивной федерации для приложения ASP.NET.
- Реализовать проверки авторизации для управления доступом к ресурсам приложения.В следующих разделах мы обсудим средства поддержки пассивной федерации в WIF, пошагово пройдем этапы настройки для этого простого сценария, а затем рассмотрим другие практические соображения для этого и других сценариев.
Средства поддержки пассивной федерации в WIF
Прежде чем обсуждать реализацию, рассмотрим средства WIF, особенно полезные для объединения идентификаций внутри ваших приложений ASP.NET в федерацию. Начнем с того, что WIF предоставляет следующие HTTP-модули.
- WSFederationAuthenticationModule (FAM) Обеспечивает поддержку федерации на основе браузера, управляет перенаправлением соответствующему STS для аутентификации и выдачи маркеров и обрабатывает конечный ответ на запрос входа для извлечения содержимого сгенерированного маркера защиты в ClaimsPrincipal, который применяется для авторизации. Этот модуль также обрабатывает другие важные федеративные сообщения, такие как запросы на выход.
- SessionAuthenticationModule (SAM) Управляет аутентифицированным сеансом.При этом генерирует сеансовый маркер защиты, содержащий ClaimsPrincipal, записывает его в cookie, контролирует срок действия сеансового cookie и восстанавливает ClaimsPrincipal из cookie, когда тот присутствует. Этот модуль также поддерживает локальный кеш сеансовых маркеров.
- ClaimsAuthorizatonModule Предоставляет точку расширения для установки собственного ClaimsAuthorizationManager, что может быть полезно для централизации проверок прав на доступ.
- ClaimsPrincipalHttpModule Создает ClaimsPrincipal на основе идентификации текущего пользователя и подключает его к потоку запроса. Кроме того, предоставляет точку расширения для установки собственного ClaimsAuthenticationManager, что может быть полезно для дополнительной настройки ClaimsPrincipal, подключаемого к потоку запроса.
ClaimsPrincipalHttpModule наиболее полезен для приложений, не использующих пассивную федерацию. Можете считать его удобным инструментом для реализации модели защиты на основе заявок в приложении ASP.NET до перехода на пассивную федерацию. Я описывала этот вариант для WCF в предыдущей статье.
Остальные три модуля обычно применяются в пассивной федерации совместно, хотя ClaimsAuthorizationModule не является обязательным. Рис. 2 иллюстрирует роль этих основных модулей в конвейере обработки запросов и их функции в типичном запросе федеративной аутентификации.
Увеличить
Рис. 2. Компоненты WIF и модули HTTP, участвующие в пассивной федерации
Учитывая схему пассивной федерации на рис. 1, первая попытка пользователя перейти на защищенную страницу в RP (1) будет отклонена. FAM обрабатывает неавторизованные запросы, формирует сообщение запроса на вход и перенаправляет пользователя в IP-STS (2). Тот аутентифицирует пользователя (3), создает ответ на запрос входа, включающий выданный маркер защиты, и перенаправляет обратно в приложение RP (4).
FAM обрабатывает ответ на запрос входа, обеспечивая, что ответ содержит действительный маркер защиты для аутентифицированного пользователя, и извлекает ClaimsPrincipal из запроса входа (5). Тем самым задается участник подсистемы безопасности (security principal) для потока запроса и HttpContext. Затем FAM с помощью SAM сериализует ClaimsPrincipal в HTTP-cookie (6), который будет предоставляться с последующими запросами в течение сеанса браузера. Если установлен ClaimsAuthorizationModule, он запустит сконфигурированный ClaimsAuthorizationManager, что позволяет выполнять глобальные проверки (7) применительно к ClaimsPrincipal до получения доступа к запрошенному ресурсу.
После предоставления запрошенного ресурса управление доступом можно реализовать с помощью традиционных ASP.NET-элементов управления входом, проверок IsInRole и другого кода, который запрашивает заявки для данного пользователя (8).
При последующих запросах передается маркер сеанса с cookie, ранее записанным SAM (9). На этот раз SAM участвует в проверке маркера сеанса и восстанавливает ClaimsPrincipal из маркера (10). FAM в этом процессе не участвует, если только не запрашивается вход, выход или не отклоняется доступ, что возможно в случае отсутствия или окончания срока действия маркера сеанса.
В дополнение к этим модулям существует два элемента управления ASP.NET, также полезных при использовании пассивной федерации.
- Элемент управления FederatedPassiveSignIn: Можно использовать вместо FAM, если приложение будет перенаправлять все неавторизованные вызовы на страницу входа с этим элементом управления, только когда требуется аутентификация. Это предполагает участие пользователя в процессе входа; это полезно при многоступенчатой аутентификации, где у пользователя запрашиваются основные удостоверения, а потом дополнительные, как того требует приложение. Этот элемент управления обеспечивает перенаправление в STS, обрабатывая ответ на запрос входа, инициализируя ClaimsPrincipal на основе ответа и устанавливая защищенный сеанс с помощью функциональности, которая предоставляется FAM и SAM.
- Элемент управления FederatedPassiveSignInStatus: Обеспечивает интерактивный вход или выход из RP-приложения, в том числе поддерживая федеративный выход.
На рис. 3 показано, как меняется поток коммуникаций при использовании элемента управления FederatedPassiveSignIn. Приложение полагается на аутентификацию на основе форм для защиты ресурсов и перенаправляет на страницу входа, где размещен этот элемент управления (1). Пользователь щелкает элемент FederatedPassiveSignIn (или перенаправляется в него автоматически), что инициирует перенаправление в STS (2). Элемент управления получает ответ от STS, полагаясь на FAM и SAM в обработке ответа на запрос входа (3), извлечении содержимого ClaimsPrincipal и записи сеансового cookie (4). Когда пользователь перенаправляется на изначально запрошенную страницу (5), SAM участвует в проверке сеансового cookie и извлекает ClaimsPrincipal для запроса. В этот момент ClaimsAuthorizationModule и страница могут выполнять свои проверки авторизации, как показано на рис. 2.
Рис. 3. Пассивная федерация с элементом управления FederatedPassive-SignIn
При обработке входящих маркеров FAM и SAM опираются на соответствующий тип SecurityTokenHandler. Когда приходит ответ на запрос входа, FAM перебирает SecurityTokenHandlerCollection в поисках корректного обработчика для данного маркера, чтобы считать его XML. В федеративном сценарии это, как правило, Saml11SecurityTokenHandler или Saml2SecurityTokenHandler, хотя могут применяться и другие форматы маркеров, если вы добавите собственные обработчики. В случае SAM для обработки маркера сеанса, сопоставленного с сеансовым cookie используется SessionSecurityTokenHandler.
Для потока коммуникаций в пассивной федерации весьма значимы конфигурационные параметры модели идентификации; кроме того, они используются для инициализации FAM, SAM и элемента управления FederatedPassiveSignIn (хотя последний также предоставляет свойства, которые можно настраивать в дизайнере Visual Studio). Вы можете передать экземпляр типа ServiceConfiguration из пространства имен Microsoft.IdentityModel.Configuration или предоставить декларативную конфигурацию в разделе <microsoft.identityModel>. На рис. 4 суммированы параметры модели идентификации, многие из которых будут обсуждаться в следующих разделах.
Рис. 4. Сводка необходимых элементов <microsoft.identityModel>
Раздел | Описание |
<issuerNameRegistry> | Указывает список доверяемых издателей сертификатов. Этот список в основном полезен для подтверждения сигнатуры маркера, чтобы маркеры, подписанные недоверенными сертификатами, были отклонены. |
<audienceUris> | Указывает список действительных URI аудитории для входящих маркеров SAML. Может быть отключен для разрешения всех URI, однако это не рекомендуется. |
<securityTokenHandlers> | Настройка параметров конфигурации для обработчиков маркеров или предоставление собственных обработчиков маркеров для управления подтверждением, проверкой подлинности и сериализацией маркеров. |
<maximumClockSkew> | Настройка допустимой разницы времени между маркерами и серверами приложений для допустимости маркеров. Разница по умолчанию – 5 минут. |
<certificateValidation> | Управление подтверждением сертификатов. |
<serviceCertificate> | Предоставление сертификата службы для расшифровки входящих маркеров. |
<claimsAuthenticationManager> | Предоставление собственного типа ClaimsAuthenticationManager для настройки или замена типа IClaimsPrincipal для прикрепления к потоку запроса. |
<claimsAuthorizationManager> | Предоставление собственного типа ClaimsAuthorizationManager для управления доступном к функциям из центрального компонента. |
<federatedAuthentication> | Предоставление параметров для пассивной федерации. |
Включение поддержки пассивной федерации
WIF упрощает настройку пассивной федерации для ваших приложений ASP.NET. STS должен предоставлять метаданные федерации (как описано в спецификации WS-Federation), а WIF — Federation Utility (FedUtil.exe), которая использует эти метаданные для установления доверительных отношений между RP и STS (помимо всего прочего, полезного как в активной, так и в пассивной федерации). Вы можете вызвать FedUtil из командной строки или из Visual Studio, щелкнув правой кнопкой мыши проект RP и выбрав ссылку Add STS.
С помощью мастера FedUtil вы выполните следующие простые операции:
- на первой странице мастера можно подтвердить изменение конфигурационного файла мастером и указать URI для RP-приложения;
- на второй странице запрашивается путь к XML-документу метаданных федерации для STS, с которой RP установит отношения доверия;
- на третьей странице вы указываете сертификат, который должен использоваться для расшифровки маркеров;
- на последней странице показывается список заявок, предлагаемых STS; его можно использовать, например, для планирования решений по управлению доступом.
Когда мастер закончит работу, FedUtil модифицирует проект, чтобы добавить ссылку на сборку Microsoft.IdentityModel. Она также изменит web.config, чтобы установить модули FAM и SAM и задать конфигурационные параметры модели идентификации для этих модулей. Приложение теперь поддерживает пассивную федерацию и будет перенаправлять неавторизованные запросы доверяемому STS.
Здесь делается допущение, что STS уже знает о RP, поэтому он будет выпускать маркеры для аутентифицированных пользователей, которые пытаются получить доступ к RP, и, конечно, что у него есть открытый ключ, требуемый RP от STS для шифрования маркеров. Это простой способ начальной подготовки ваших приложений ASP.NET к объединению в федерацию. Разумеется, это помогает понять всю настройку с нуля на случай, если вам понадобится своя конфигурация, а также выйти за пределы базовой настройки, обеспечиваемой мастером. С этого момента я сосредоточусь на подходе «с нуля».
Без использования FedUtil нужно вручную добавить ссылку на сборку Microsoft.IdentityModel и самостоятельно сконфигурировать модули FAM и SAM, а также настроить необходимые параметры модели идентификации. HTTP-модули добавляются в два раздела: system.web для Internet Information Services (IIS) 6 и system.webServer для IIS 7. Если приложение размещается в IIS 7, то WIF-модули конфигурируются так:
<modules>
<!--other modules-->
<add name="SessionAuthenticationModule"
type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
preCondition="managedHandler" />
<add name="WSFederationAuthenticationModule"
type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
preCondition="managedHandler" />
</modules>
По умолчанию эта конфигурация будет защищать только ресурсы с расширениями, явно сопоставленными с конвейером ASP.NET для обработки (.aspx, .asax, и др.). Чтобы защитить дополнительные ресурсы с помощью федеративной аутентификации, вы должны сопоставить соответствующие расширения с конвейером ASP.NET в IIS или установить runAllManagedModulesForAllRequests в true в настройках модулей (только в IIS 7):
<modules runAllManagedModulesForAllRequests="true">
Чтобы начал работать FAM, вы должны также задать режим аутентификации ASP.NET как None и отклонять доступ анонимных пользователей к ресурсам приложений:
<authentication mode="None" />
<authorization>
<deny users="?" />
</authorization>
Оба модуля полагаются на конфигурационные параметры модели идентификации, описанные на рис. 4, — это типичный случай, и один из примеров показан на рис. 5. Большинство этих параметров генерируется за вас утилитой FedUtil за исключением certificateValidation и нескольких параметров в federatedAuthentication. Обычно я рекомендую использовать режим проверки сертификатов PeerTrust — он требует явным образом добавлять все доверяемые сертификаты, включая сертификат доверенного издателя, в хранилище TrustedPeople на локальном компьютере.
Рис. 5. Конфигурация модели идентификации для пассивной федерации
<microsoft.identityModel>
<service>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<trustedIssuers>
<add thumbprint="EF38A0A6D1274766093D3D78BFE4ECA77C62D5C3"
name="http://localhost:60768/STS/" />
</trustedIssuers>
</issuerNameRegistry>
<certificateValidation certificateValidationMode="PeerTrust"
revocationMode="Online" trustedStoreLocation="LocalMachine"/>
<audienceUris>
<add value="http://localhost:50652/ClaimsAwareWebSite2/" />
</audienceUris>
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true"
issuer="http://localhost:60768/STS/"
realm="http://localhost:50652/ClaimsAwareWebSite2/"
requireHttps="true" />
<cookieHandler requireSsl="true" name="FedAuth"
hideFromScript="true" path="/ClaimsAwareWebSite2" />
</federatedAuthentication>
<serviceCertificate>
<certificateReference x509FindType="FindByThumbprint"
findValue="8A90354199D284FEDCBCBF1BBA81BA82F80690F2"
storeLocation="LocalMachine" storeName="My" />
</serviceCertificate>
</service>
</microsoft.identityModel>
Как правило, вы должны требовать применения HTTPS/SSL для пассивной федерации, чтобы защищать выпускаемые маркеры от атак с посредником (man-in-the-middle attacks), а также для сеансовых cookie. По умолчанию cookie скрыты от сценария, но это важная настройка, и именно поэтому я самостоятельно задаю ее на рис. 4.
Что касается имени и пути cookie, то по умолчанию ему присваивается имя FedAuth, а путем считается путь к каталогу приложения. Может оказаться полезным указать для cookie уникальное имя, если, например, в решении много RP-приложений, совместно использующих один и тот же домен. И наоборот, вы предпочтете задать универсальный путь, если вам нужно, чтобы все cookie совместно использовались несколькими приложениями в одном домене.
Обычно вы используете FedUtil для объединения своих приложений ASP.NET в пассивную федерацию с применением FAM и SAM, а затем более тонко настраиваете соответствующие параметры согласно требованиям решения. Вы также можете задействовать элемент управления PassiveFederationSignIn вместо FAM, как показано на рис. 3. Этот элемент управления может загружать свои настройки из раздела microsoft.identityModel или же вы сами можете напрямую задавать его свойства.
Подход на основе этого элемента управления удобен, если вы хотите, чтобы неавторизованные запросы перенаправлялись на страницу входа и чтобы пользователь явным образом входил в федерацию, щелкнув элемент управления, — без автоматического перенаправления к STS с помощью FAM. Например, если пользователь может относиться более чем к одному провайдеру идентификаций, в страницу входа можно было бы заложить механизм, который позволял бы выбирать этот вариант до перенаправления к STS. О таком механизме мы еще поговорим.
Выдача маркеров в пассивной федерации
Как упоминалось, пассивная федерация полагается на HTTP-команды GET и POST и перенаправления браузера при взаимодействии между RP и STS. На рис. 5 показаны основные параметры, которые включаются в запрос входа и ответ на запрос входа и учитываются в ходе этого процесса.
Рис. 6. Основные параметры запроса входа и ответа на него, участвующие в запросах пассивной федерации
Получив запрос входа, STS проверяет, известна ли ему RP, сверяя параметр wtrealm по своему списку известных RP. Предпочтительнее, чтобы STS заранее знал о данной RP, сертификате, который потребуется для шифрования маркера, и любых ожиданиях RP в отношении нужных заявок, которые должны быть включены в выдаваемый маркер. RP может указать, какие заявки ей требуются, через необязательный параметр wreq с полным запросом входа, а STS может либо учитывать этот список, либо самостоятельно решать, какие заявки включать в зависимости от конкретного аутентифицированного пользователя.
В простом варианте пассивной федерации, представленном на рис. 1, за аутентификацию пользователей отвечают единственная RP и единственный IP-STS. Если IP-STS аутентифицирует пользователей в домене Windows, он может выдавать заявки ролей вроде Admin, User или Guest. Здесь делается допущение, что эти роли имеют смысл для RP при авторизации. В следующем разделе я буду считать набор этих ролей достаточным и рассмотрю способы авторизации. Потом мы обсудим преобразование заявок в RP при необходимости конвертации STS-заявок в нечто более полезное для авторизации.
Авторизация на основе заявок
Как я уже говорила в своей предыдущей статье, защита на основе ролей в .NET Framework предполагает, что к каждому потоку подключается участник системы безопасности (security principal). Этот участник, основанный на IPrincipal, обертывает идентификацию аутентифицированного пользователя в реализации IIdentity. WIF предоставляет типы ClaimsPrincipal и ClaimsIdentity, основанные на IClaimsPrincipal и IClaimsIdentity (которые в конечном счете являются производными от IPrincipal и IIdentity). Обрабатывая ответ на запрос входа, FAM извлекает ClaimsPrincipal из выданного маркера защиты. Аналогично SAM извлекает ClaimsPrincipal из сеансового cookie. Этот ClaimsPrincipal играет центральную роль в WIF-авторизации для вашего приложения ASP.NET.
Вы можете использовать любой из следующих подходов к авторизации:
- применять параметры авторизации, специфичные для месторасположения, для разграничения доступа к каталогам или ресурсам индивидуальных приложений;
- применять ASP.NET-элементы управления входом, такие как LoginView, для контроля за доступом к функциональности;
- с помощью ClaimsPrincipal выполнять динамические проверки IsInRole (например, для динамического скрытия или отображения UI-элементов);
- с помощью типа PrincipalPermission выполнять динамические запросы на разрешения или использовать PrincipalPermissionAttribute, если для вас предпочтительнее декларативный запрос разрешений;
- предоставлять собственный ClaimsAuthorizationManager для централизованных проверок прав доступа еще до загрузки запрошенного ресурса.
Первые три варианта опираются на метод IsInRole, предоставляемый типом ClaimsPrincipal. Вы должны выбрать тип заявок ролей, подходящий для проверки IsInRole, чтобы при управлении доступом использовались корректные заявки. В WIF тип заявок ролей по умолчанию такой:
http://schemas.microsoft.com/ws/2008/06/identity/claims/role
Если ClaimsPrincipal включает отклоненные заявки, тип заявок ролей должен соответствовать тому, который предлагается по умолчанию. Позже я опишу заявки разрешений в контексте преобразования заявок. При этом вы должны указывать тип заявок разрешений так же, как и тип заявок ролей, чтобы можно было использовать IsInRole.
Вы можете управлять доступом к конкретным страницам или каталогам глобально, с помощью файла web.config file. В корне приложения создайте тег location, указывающий защищаемый путь, перечислите допустимые роли и отклоняйте попытки доступа любых других пользователей. Ниже показано, как разрешить доступ к файлам в каталоге AdminOnly только членам группы Administrators:
<location path="AdminOnly">
<system.web>
<authorization>
<allow roles="Administrators" />
<deny users="*"/>
</authorization>
</system.web>
</location>
В качестве альтернативы можно поместить файл web.config в любой подкаталог и указать правила авторизации. Размещение следующей конфигурации в каталоге AdminOnly даст тот же результат:
<configuration>
<system.web>
<authorization >
<allow roles="Administrators" />
<deny users="*"/>
</authorization>
</system.web>
</configuration>
Чтобы динамически скрывать и показывать UI-компоненты или иным образом управлять доступом к элементам страницы, вы можете задействовать средства контроля ролей в элементах управления, например в LoginView. Однако большинство разработчиков предпочитает явным образом задавать свойства элементов управления для контроля доступа при загрузке страницы, что обеспечивает более тонкий контроль. Для этого вы можете вызвать метод IsInRole, предоставляемый ClaimsPrincipal. Чтобы обратиться к текущему участнику системы безопасности, используйте статическое свойство Thread.CurrentPrincipal следующим образом:
if (!Thread.CurrentPrincipal.IsInRole("Administrators"))
throw new SecurityException("Access is denied.");
Помимо явных проверок IsInRole в период выполнения, можно также написать традиционные запросы разрешений на основе ролей, используя тип PrincipalPermission. Вы инициализируете этот тип заявкой требуемой роли (второй параметр конструктора), и при вызове Demand вызывается метод IsInRole текущего участника. Если заявка не обнаружена, генерируется исключение:
PrincipalPermission p =
new PrincipalPermission("", "Administrators");
p.Demand();
Этот подход удобен для отклонения запроса исключением, когда соответствующих ролей нет.
Также удобно централизовать проверки авторизации, общие для всех запрашиваемых ресурсов. Иногда, если у вас действует политика управления доступом (например, правила, хранящиеся в базе данных), вы можете использовать централизованный компонент для считывания этих правил и тем самым контролировать доступ к функциональности и ресурсам. С этой целью в WIF включен компонент ClaimsAuthorizationManager, который можно расширять. Вспомните из моей предыдущей статьи, что этот тип собственного компонента можно настраивать в разделе модели идентификации:
<microsoft.identityModel>
<service>
<!--other settings-->
<claimsAuthorizationManager
type="CustomClaimsAuthorizationManager"/>
</service>
</microsoft.identityModel>
На рис. 7 показан собственный ClaimsAuthorizationManager, проверяющий, имеется ли заявка имени и находится ли запрашиваемый ресурс в каталоге AdminsOnly, для доступа к которому нужна заявка роли Administrators.
Рис. 7. Собственная реализация ClaimsAuthorizationManager
public class CustomClaimsAuthorizationManager:
ClaimsAuthorizationManager {
public CustomClaimsAuthorizationManager()
{ }
public override bool CheckAccess(
AuthorizationContext context) {
ClaimsIdentity claimsIdentity =
context.Principal.Identity as ClaimsIdentity;
if (claimsIdentity.Claims.Where(
x => x.ClaimType == ClaimTypes.Name).Count() <= 0)
throw new SecurityException("Access is denied.");
IEnumerable<Claim> resourceClaims =
context.Resource.Where(x=>x.ClaimType==ClaimTypes.Name);
if (resourceClaims.Count() > 0) {
foreach (Claim c in resourceClaims) {
if (c.Value.Contains("\AdminOnly") &&
!context.Principal.IsInRole("Administrators"))
throw new SecurityException("Access is denied.");
}
}
return true;
}
}
CustomClaimsAuthorizationManager переопределяет CheckAccess для предоставления такой функциональности. Этот метод принимает параметр AuthorizationContext, который сообщает информацию о запрошенном действии (в случае пассивной федерации это HTTP-команда, например GET или POST), запрошенном ресурсе (URI) и ClaimsPrincipal, еще не подключенный к потоку запроса.
Преобразование заявок
Зачастую заявки, выдаваемые IP-STS, хоть и полезны для описания аутентифицированного пользователя, не соответствуют требованиям к авторизации в RP. IdP не обязан знать, какой тип ролей, разрешений или других деталей требуется для авторизации в каждой RP. Задача IdP — выдавать заявки, релевантные для домена провайдера идентификаций, т. е. заявки, которые IdP может назначить аутентифицированному пользователю.
RP может понадобиться преобразование заявок от IP-STS в нечто, более адекватное для авторизации. Это подразумевает, что RP может сопоставить идентификацию пользователя (например, имя пользователя или UPN) с набором заявок RP. Предполагая, что IP-STS выдает заявки ролей по умолчанию, на рис. 8 перечислен возможный набор заявок разрешений, которые RP могла бы выдать на основе каждой входящей заявки роли. Тип заявки разрешения может быть нестандартным и определенным в RP так:
urn:ClaimsAwareWebSite/2010/01/claims/permission
Хорошее место для преобразования входящих заявок от IP-STS — собственный ClaimsAuthenticationManager. Вы можете установить его, добавив в раздел microsoft.identityModel:
<microsoft.identityModel>
<service>
<!--other settings-->
<claimsAuthenticationManager
type="CustomClaimsAuthenticationManager"/>
</service>
</microsoft.identityModel>
На рис. 9 показан пример CustomClaimsAuthenticationManager, который преобразует входящие заявки ролей, выданные IP-STS, в заявки разрешений, релевантных для RP.
Рис. 8. Преобразование заявок ролей в заявки разрешений в RP
Заявка роли | Заявки разрешений |
Администраторы | Создание, чтение, обновление, удаление |
Пользователи | Создание, чтение, обновление |
Гость | Чтение |
Рис. 9. Преобразование собственных заявок в RP
public class CustomClaimsAuthenticationManager:
ClaimsAuthenticationManager {
public CustomClaimsAuthenticationManager() { }
public override IClaimsPrincipal Authenticate(
string resourceName, IClaimsPrincipal incomingPrincipal) {
IClaimsPrincipal cp = incomingPrincipal;
ClaimsIdentityCollection claimsIds =
new ClaimsIdentityCollection();
if (incomingPrincipal != null &&
incomingPrincipal.Identity.IsAuthenticated == true) {
ClaimsIdentity newClaimsId = new ClaimsIdentity(
"CustomClaimsAuthenticationManager", ClaimTypes.Name,
"urn:ClaimsAwareWebSite/2010/01/claims/permission");
ClaimsIdentity claimsId =
incomingPrincipal.Identity as ClaimsIdentity;
foreach (Claim c in claimsId.Claims)
newClaimsId.Claims.Add(new Claim(
c.ClaimType, c.Value, c.ValueType,
"CustomClaimsAuthenticationManager", c.Issuer));
if (incomingPrincipal.IsInRole("Administrators")) {
newClaimsId.Claims.Add(new Claim(
"urn:ClaimsAwareWebSite/2010/01/claims/permission",
"Create"));
newClaimsId.Claims.Add(new Claim(
"urn:ClaimsAwareWebSite/2010/01/claims/permission",
"Read"));
newClaimsId.Claims.Add(new Claim(
"urn:ClaimsAwareWebSite/2010/01/claims/permission",
"Update"));
newClaimsId.Claims.Add(new Claim(
"urn:ClaimsAwareWebSite/2010/01/claims/permission",
"Delete"));
}
else if (incomingPrincipal.IsInRole("Users")) {
newClaimsId.Claims.Add(new Claim(
"urn:ClaimsAwareWebSite/2010/01/claims/permission",
"Create"));
newClaimsId.Claims.Add(new Claim(
"urn:ClaimsAwareWebSite/2010/01/claims/permission",
"Read"));
newClaimsId.Claims.Add(new Claim(
"urn:ClaimsAwareWebSite/2010/01/claims/permission",
"Update"));
}
else {
newClaimsId.Claims.Add(new Claim(
"urn:ClaimsAwareWebSite/2010/01/claims/permission",
"Read"));
}
claimsIds.Add(newClaimsId);
cp = new ClaimsPrincipal(claimsIds);
}
return cp;
}
}
Чтобы проверки IsInRole (описанные ранее) работали, вы должны передать тип заявок разрешений как тип заявок ролей. На рис. 9 это указывается при конструировании ClaimsIdentity, так как RP создает именно ClaimsIdentity.
Когда источником заявок являются входящие SAML-маркеры, вы можете передать тип заявок ролей в SecurityTokenHandler. Следующий код иллюстрирует, как декларативно сконфигурировать Saml11SecurityTokenHandler на использование типа заявок разрешений в качестве типа заявок ролей:
<microsoft.identityModel>
<service>
<!--other settings-->
<securityTokenHandlers>
<remove type="Microsoft.IdentityModel.Tokens.Saml11.Saml11SecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add type="Microsoft.IdentityModel.Tokens.Saml11.Saml11SecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<samlSecurityTokenRequirement >
<roleClaimType
value= "urn:ClaimsAwareWebSite/2010/01/claims/permission"/>
</samlSecurityTokenRequirement>
</add>
</securityTokenHandlers>
</service>
</microsoft.identityModel>
Для обработчиков SAML-маркеров имеется раздел samlSecurityTokenRequirement, где вы можете задать имя и тип заявок ролей, а также другие параметры, относящиеся к проверке сертификатов и маркеров Windows.
Распознавание внутренней области
До сих пор я уделяла основное внимание простому варианту федерации с единственным IP-STS. Такой вариант предполагает, что RP будет всегда перенаправлять пользователей к конкретному IP-STS для аутентификации.
Однако в федерации RP может доверять нескольким издателям маркеров из нескольких доменов. В этом случае возникает совершенно новая проблема, поскольку RP нужно решать, какой IP-STS должен аутентифицировать конкретных пользователей, запрашивающих доступ к ресурсам. Домен, в котором аутентифицируется пользователь, является внутренней областью этого пользователя (home realm), и поэтому такой процесс называют распознаванием внутренней области (home realm discovery).
Существует ряд механизмов, которые можно использовать в приложении для распознавания внутренней области:
- как в текущем примере, внутренняя область известна заранее, и поэтому запросы всегда перенаправляются конкретному IP-STS;
- пользователи могут переходить к RP с другого портала, который может предоставлять строку запроса, указывающую внутреннюю область для пользователей с этого портала;
- The RP может требовать, чтобы пользователи попадали на начальную страницу, специфичную для каждой внутренней области. Целевая страница может предполагать определенную внутреннюю область.
- RP может уметь определять внутреннюю область по IP-адресу запроса или с применением других эвристических методов;
- если RP не может определить внутреннюю область с помощью одного из упомянутых выше способов, он может выводить UI, где пользователь выбирает свою внутреннюю область или предоставляет информацию, которая помогает RP определить эту область;
- если RP поддерживает информационные карты (information cards), выбор конкретной карты позволяет перенаправить пользователя на аутентификацию в соответствующую внутреннюю область с использованием активной федерации;
- WS-Federation кратко описывает, как реализовать сервис распознавания внутренней области, но четкой спецификации такой реализации нет.
Независимо от способа распознавания внутренней области цель — перенаправить пользователя для аутентификации к корректному IP-STS. Здесь возможно несколько вариантов. В одном из них RP может потребоваться динамически задавать URI издателя, чтобы запросы входа посылались правильному IP-STS. В этом случае RP должен перечислять все доверяемые IP-STS в разделе trustedIssuers, например:
<trustedIssuers>
<add thumbprint="6b887123330ae8d26c3e2ea3bb7a489fd609a076"
name="IP1" />
<add thumbprint="d5bf17e2bf84cf2b35a86ea967ebab838d3d0747"
name="IP2" />
</trustedIssuers>
Кроме того, вы можете переопределить событие RedirectingToIdentityProvider, предоставляемое FAM и, используя подходящую эвристическую логику, определить корректный URI для STS. С этой целью поместите следующий код в реализацию Global.asax:
void WSFederationAuthenticationModule_RedirectingToIdentityProvider(
object sender, RedirectingToIdentityProviderEventArgs e) {
if (e.SignInRequestMessage.RequestUrl.Contains(
"IP1RealmEntry.aspx")) {
e.SignInRequestMessage.BaseUri =
new Uri("https://localhost/IP1/STS/Default.aspx");
}
else if (e.SignInRequestMessage.RequestUrl.Contains(
"IP2RealmEntry.aspx")) {
e.SignInRequestMessage.BaseUri = new Uri(
"https://localhost/IP2/STS/Default.aspx");
}
}
Другой вариант включает передачу параметра внутренней области (whr) вместе с запросом входа основному STS. У RP может быть, например, Resource STS (R-STS или RP-STS), ответственный за преобразование заявок. RP-STS не аутентифицирует пользователей (это не IdP), но у него есть доверительные отношения с одним или несколькими IdP.
RP имеет доверительные отношения с RP-STS и всегда принимает маркеры, изданные RP-STS. Последний отвечает за перенаправление каждого запроса к правильному IdP. RP-STS может определить подходящий IP-STS, как в приведенном выше коде, но есть и другой вариант:RP может передать RP-STS информацию о внутренней области в параметре whr. В этом случае RP динамически задает параметр whr:
void WSFederationAuthenticationModule_RedirectingToIdentityProvider(
object sender, RedirectingToIdentityProviderEventArgs e) {
if (e.SignInRequestMessage.RequestUrl.Contains(
"IP1RealmEntry.aspx")) {
e.SignInRequestMessage.HomeRealm =
"https://localhost/IP1/STS/Default.aspx";
}
else if (e.SignInRequestMessage.RequestUrl.Contains(
"IP2RealmEntry.aspx")) {
e.SignInRequestMessage.HomeRealm =
"https://localhost/IP2/STS/Default.aspx";
}
}
RP-STS использует этот параметр для перенаправления к правильному IP-STS и последующего преобразования заявок от IP-STS в заявки, релевантные для RP.
Единый вход и единый выход
Единый вход (single sign-on) и единый выход (single sign-out) — важные элементы федерации. Единый вход является механизмом, который позволяет аутентифицированным пользователям обращаться к нескольким RP-приложения, пройдя всего одну проверку подлинности. Единый выход, как и подразумевает его название, обеспечивает выход изо всех RP-приложений и любой цепочки STS с помощью одного запроса.
В простом варианте федерации, показанном на рис. 1, пользователь аутентифицируется IP-STS и авторизуется RP на основе выданного маркера защиты. После аутентификации браузер пользователя получает сеансовый cookie для STS и еще один для RP. Теперь, если пользователь перейдет к другой RP, он будет переадресован к IP-STS для очередной аутентификации при условии, что оба RP-приложения доверяют одному и тому же IP-STS. Поскольку у пользователя уже установлен сеанс с IP-STS, STS выдаст маркер для второй RP, не требуя передачи удостоверений. После этого пользователь получает доступ ко второй RP и новый сеансовый cookie для второй RP.
Как уже обсуждалось, WIF предоставляет SAM для выдачи сеансового cookie аутентифицированному пользователю. По умолчанию этот сеансовый cookie выдается по относительному к домену адресу приложения, и его базовое имя — FedAuth. Так как федеративные сеансовые cookie могут быть довольно объемными, маркер обычно разделяется на два и более cookie: FedAuth, FedAuth1 и т. д.
Если у вас в одном домене более одного приложения, то по умолчанию браузер будет получать cookie с именем FedAuth для каждой RP (рис. 10). Браузер посылает только те cookie, которые сопоставлены с данным доменом и путем для запроса.
Увеличить
Рис. 10. Сеансовые Cookies, связанные с каждым RP и STS
Это поведение по умолчанию, как правило, подходит в большинстве случаев, но иногда нужно формировать уникальное имя сеансового cookie для каждого приложения, в частности, если они размещены в одном домене. Или же несколько приложений в одном домене могут совместно использовать сеансовый cookie, в каковом случае вы можете указать путь для этого cookie в виде «/».
Если срок действия сеансового cookie истекает, браузер удаляет его из кеша и пользователь вновь перенаправляется к STS для аутентификации. Кроме того, если истекает срок действия выданного маркера, сопоставленного с сеансовым cookie, WIF перенаправляет к STS для получения нового маркера.
Выход осуществляется более явным образом — обычно он инициируется пользователем. Единый выход — дополнительная (необязательная) возможность, описанная в спецификации WS-Federation; она предполагает, что STS должен также уведомлять RP-приложения, для которых он выдавал маркеры, о запросе выхода. Тем самым сеансовый cookie удаляется для всех приложений, в которые заходил пользователь в течение сеанса единого входа. В более сложных случаях при наличии нескольких STS основной STS, принимающий запрос выхода, должен уведомлять об этом остальные STS.
Я сосредоточусь на том, что вы должны делать в RP для поддержки федеративного единого выхода. Вы можете поместить элемент управления FederatedPassiveSignInStatus на любую страницу, с которой вы хотите поддерживать вход и выход, и этот элемент управления будет автоматически сообщать о своем состоянии. После входа элемент управления предоставляет ссылку, кнопку или изображение для выхода.
Когда вы щелкаете этот элемент управления, он обрабатывает выход согласно свойству SignOutAction, которое может принимать значения Refresh, Redirect, RedirectToLoginPage или FederatedPassiveSignOut. Первые три приводят к удалению сеансового cookie для приложения, но не уведомляют STS о запросе выхода. При выборе значения FederatedPassiveSignOut, элемент управления вызывает SignOut из WSFederationAuthenticationModule. Это гарантирует удаление федеративных сеансовых cookie для приложения. Кроме того, посылается запрос выхода в STS:
GET https://localhost/IP1/STS?wa=wsignout1.0
Если вы не используете элемент управления FederatedPassiveSignInStatus, то можете напрямую вызывать WSFederationAuthenticationModule.SignOut для инициации перенаправления к STS с запросом выхода.
Единый выход подразумевает, что пользователь выходит изо всех приложений, в которые он входил под своей федеративной идентификацией. Если STS поддерживает это, он должен хранить список RP-приложений, в которые входил пользователь в течение сеанса, и выдавать запрос на очистку каждой RP при инициации федеративной единого выхода:
GET https://localhost/ClaimsAwareWebSite?wa=wsignoutcleanup1.0
В более сложных случаях тот же запрос на очистку должен посылаться любым другим STS, участвующим в федеративном сеансе. В связи с этим STS должен заранее знать об URI запроса очистки для каждой RP и STS. Для поддержки единого выхода ваши RP должны уметь обрабатывать эти запросы на очистку. Как FAM, так и элемент управления FederatedPassiveSignInStatus поддерживают это. Если вы используете FAM, запрос на очистку может быть отправлен на любой URI в RP, и FAM обработает этот запрос и очистит любые сеансовые cookie. Если же вы применяете элемент управления FederatedPassiveSignInStatus, запрос на очистку должен быть отправлен на странице, содержащую данный элемент управления.
По сути, в спецификации WS-Federation не детализируется, как реализовать единый выход и очистку. В итоге гарантировать корректный единый выход между всеми партнерами по федерации затруднительно, но, если вы являетесь владельцем среды и хотите добиться реализации единого выхода, это вполне достижимая цель.