Ну, я считаю, что я сделаю удар по своему собственному вопросу сейчас, когда я провел несколько часов, играя с различными подходами.
Мой первый подход состоял в том, чтобы настроить аутентификацию на основе сертификатов между службой WCF и общедоступным веб-сайтом (веб-сайт является потребителем / клиентом службы). Несколько тестовых сертификатов, сгенерированных с помощью makecert
, добавьте их в Personal
, Trusted People
и Trusted Root Certification Authorities
(потому что я не удосужился сгенерировать настоящие сертификаты для служб сертификатов нашего домена), некоторые изменения в конфигурационном файле, и отлично, у нас все готово.
Чтобы запретить веб-сайту сохранять информацию об имени пользователя и пароле для пользователей, идея состоит в том, что после входа пользователя на веб-сайт с помощью проверки подлинности с помощью форм веб-сайт может передавать только имя пользователя (доступный через HttpContext.Current.User.Identity.Name
) как необязательный UserNameSecurityToken
в дополнение к X509CertificateSecurityToken
, который фактически используется для защиты сообщения. Если найден необязательный токен безопасности имени пользователя, то служба WCF скажет: «Эй, эта доверенная подсистема сообщает, что этот пользователь прошел надлежащую аутентификацию, поэтому позвольте мне установить MyCustomPrincipal
для этого пользователя и установить его в текущем потоке, чтобы Фактический сервисный код может проверить это ". Если это не так, то будет установлена анонимная версия MyCustomPrincipal
.
Итак, я потратил пять часов, пытаясь реализовать это, и с помощью различных блогов я смог это сделать. (Я потратил большую часть своего времени на отладку проблемы, в которой у меня были правильные настройки для каждой конфигурации и класса поддержки, а затем установил свои пользовательские права доступа после Я запустил хост, а не раньше, так что мои усилия фактически не требовали Эффект. Иногда я ненавижу компьютеры. У меня был TrustedSubsystemAuthorizationPolicy
, который прошел проверку сертификата X509, установил анонимный MyCustomPrincipal
, TrustedSubsystemImpersonationAuthorizationPolicy
, который принял токен имени пользователя с пустым паролем и установил роль клиента MyCustomPrincipal
если он увидел, что анонимный доверительный субъект подсистемы уже установлен, и UserNameAuthorizationPolicy
, который выполнял регулярную проверку на основе имени пользователя и пароля для других конечных точек, где сертификаты X509 не используются. Это сработало, и это было замечательно.
Но.
Наступил момент, когда я заколол себе глаза, когда я возился с сгенерированным клиентским прокси-кодом, который веб-сайт будет использовать для общения с этим сервисом. Задать UserName
для свойства ClientCredentials
сгенерированного объекта ClientBase<T>
было достаточно просто. Но основная проблема заключается в том, что учетные данные относятся только к ChannelFactory
, а не к конкретному вызову метода.
Видите ли, new () с клиентским прокси WCF стоит дороже , чем , как вы могли бы подумать . Я написал быстрое и грязное приложение для тестирования производительности: и new (), вызывающий новый прокси и вызывающий метод десять раз, занимал около 6 секунд, тогда как new () вызывал прокси один раз и вызывал только метод, в 10 раз дороже. около 3/5 одной секунды. Это просто удручающая разница в производительности.
Так что я могу просто реализовать пул или кеш для клиентского прокси, верно? Ну, нет, это нелегко обойти: информация об учетных данных клиента находится на уровне фабрики каналов, потому что она может использоваться для защиты транспорта, а не только сообщения, а некоторые привязки поддерживают фактический транспорт открытым между вызовами службы. Поскольку учетные данные клиента уникальны для прокси-объекта, это означает, что мне потребуется уникальный кэшированный экземпляр для каждого пользователя, который в данный момент находится на веб-сайте. Это потенциально много прокси-объектов, находящихся в памяти, и довольно близко к проблеме, которую я пытался избежать в первую очередь! И поскольку мне все равно нужно прикоснуться к свойству Endpoint
, чтобы настроить привязку для необязательного вспомогательного токена имени пользователя, я не могу воспользоваться преимуществом автоматического кэширования фабрики каналов , которое Microsoft добавила «бесплатно» в .NET 3.5.
Возвращаясь к чертежной доске: мой второй подход, который, я думаю, я в конечном итоге пока использую, заключается в том, чтобы придерживаться безопасности сертификата X509 между клиентским веб-сайтом и службой WCF. Я просто отправлю собственный заголовок SOAP «UserName» в своих сообщениях, и служба WCF сможет проверить этот заголовок SOAP, определить, поступил ли он из доверенной подсистемы, такой как веб-сайт, и, если это так, установить MyCustomPrincipal
в так же, как и раньше.
Codeproject и случайные люди в Google - это замечательные вещи, потому что они помогли мне быстро это запустить и запустить, даже после того, как наткнулся на странную ошибку WCF, когда это приходит к пользовательскому поведению конечной точки в конфигурации . Благодаря внедрению инспекторов сообщений на стороне клиента и на стороне службы - один для добавления заголовка UserName, а другой - для его чтения и установки правильного принципала - этот код находится в одном месте, где я могу просто забыть об этом. Поскольку мне не нужно трогать свойство Endpoint
, я получаю встроенное кеширование фабрики каналов бесплатно. И поскольку ClientCredentials
одинаковы для любого пользователя, обращающегося к веб-сайту (действительно, они всегда являются сертификатом X509 - изменяется только значение заголовка UserName в самом сообщении), добавление кэширования прокси клиента или пула прокси гораздо более тривиально.
Так вот что я в итоге и сделал. Фактический код сервиса в сервисе WCF может делать что-то вроде
// Scenario 1: X509Cert + custom UserName header yields for a Web site customer ...
Console.WriteLine("{0}", Thread.CurrentPrincipal.Identity.Name); // prints out, say, "joe@example.com"
Console.WriteLine("{0}", Thread.CurrentPrincipal.IsInRole(MyRoles.Customer)); // prints out "True"
// Scenario 2: My custom UserNameSecurityToken authentication yields for an employee ...
Console.WriteLine("{0}", Thread.CurrentPrincipal.Identity.Name); // prints out, say, CN=Nick,DC=example, DC=com
Console.WriteLine("{0}", Thread.CurrentPrincipal.IsInRole(MyRoles.Employee)); // prints out "True"
// Scenario 3: Web site doesn't pass in a UserName header ...
Console.WriteLine("{0}", Thread.CurrentPrincipal.Identity.Name); // prints out nothing
Console.WriteLine("{0}", Thread.CurrentPrincipal.IsInRole(MyRoles.Guest)); // prints out "True"
Console.WriteLine("{0}", Thread.CurrentPrincipal.IsInRole(MyRoles.Customer)); // prints out "False"
Неважно, как эти люди прошли проверку подлинности, или что некоторые живут на SQL-сервере или что некоторые живут в Active Directory: PrincipalPermission.Demand
и ведение журналов для целей аудита теперь совсем несложно.
Надеюсь, это поможет бедной душе в будущем.