Веб-сайт ASP.NET + приложение Windows Forms + служба WCF: учетные данные клиента - PullRequest
18 голосов
/ 06 декабря 2008

Допустим, я рассматриваю вопрос о создании службы WCF, основной целью которой является предоставление широких услуг, которые могут использоваться тремя разнородными приложениями: общедоступным веб-сайтом, внутренним приложением Windows Forms и беспроводным мобильным устройством. Назначение сервиса двоякое: (1) консолидировать код, связанный с бизнес-процессами, в центральном месте и (2) заблокировать доступ к устаревшей базе данных, окончательно и раз и навсегда скрывая ее за одним набором сервисов.

В настоящее время каждое из трех приложений имеет свои собственные уровни персистентности и домена с немного отличающимися представлениями одной и той же базы данных. Вместо того, чтобы все три приложения обращались к базе данных, они общались со службой WCF, предоставляя новые функции некоторым клиентам (сборщик мобильных устройств в настоящее время не может запускать процессы для отправки электронной почты, очевидно) и централизовав системы уведомлений (вместо запланированное задание опроса базы данных каждые пять минут на предмет новых заказов, просто отправьте эхо-запрос на систему служебных вызовов, когда один из этих клиентов вызывает сервисный метод AcceptNewOrder()). В общем, пока это звучит довольно вменяемым.

Однако с точки зрения общего дизайна я в тупике, когда речь заходит о безопасности. Приложение Windows Forms в настоящее время просто использует принципы Windows; сотрудники хранятся в Active Directory, и после запуска приложения они могут войти в систему как текущий пользователь Windows (в этом случае пароль не требуется) или могут предоставить свое доменное имя и пароль. Мобильный клиент не имеет никакой концепции пользователя; его соединение с базой данных является жестко закодированной строкой. А на веб-сайте тысячи пользователей хранятся в устаревшей базе данных. Итак, как мне реализовать модель идентификации и настроить конечные точки WCF для решения этой проблемы?

С точки зрения приложения Windows Forms это не является большой проблемой: прокси WCF может быть инициирован один раз и может зависать в памяти, поэтому мне нужны только учетные данные клиента (и я могу запросить их снова, если прокси-сервер когда-либо неисправности). Мобильный клиент может быть только в специальном случае и использовать сертификат X509 для аутентификации на основе службы WCF. Но что мне делать с веб-сайтом?

В случае веб-сайта, анонимный доступ к некоторым услугам разрешен. А для сервисов, которые требуют аутентификации в гипотетической роли «Клиент», я, очевидно, не хочу аутентифицировать их при каждом запросе по двум причинам:

  • Мне нужно их имя пользователя и пароль каждый раз. Хранение этой пары информации практически везде - сеанс, зашифрованное печенье, луна - кажется плохой идеей.
  • Мне бы приходилось обращаться к таблице пользователей в базе данных для каждого запроса. Уч.

Единственное решение, которое я могу придумать, - это рассматривать веб-сайт как доверенную подсистему. Служба WCF ожидает определенного сертификата X509 от веб-сайта. Веб-сайт, использующий внутреннюю проверку подлинности с помощью форм (которая вызывает метод AuthenticateCustomer() для службы, возвращающей логический результат), может добавить дополнительную заявку в список учетных данных, например, "joe@example.com", вошедший в систему как покупатель." Тогда каким-то образом на сервисе можно создать собственный объект IIdentity и IPrincipal с этим утверждением, при этом служба WCF будет уверенна, что веб-сайт правильно аутентифицировал клиента (он будет знать, что утверждение не было подделано, по крайней мере, потому что он заранее узнает сертификат веб-сайта).

С учетом всего этого код службы WCF мог бы говорить что-то вроде [PrincipalPermission.Demand(Role=MyRoles.Customer)] или [PrincipalPermission.Demand(Role=MyRoles.Manager)], а в Thread.CurrentPrincipal было бы что-то, представляющее пользователя (адрес электронной почты для клиента или отличительное имя для сотрудника, оба они полезны для регистрации и аудита).

Другими словами, для каждой службы будут существовать две разные конечные точки: одна, которая принимает известные клиентские сертификаты X509 (для мобильных устройств и веб-сайт), и другая, которая принимает пользователей Windows (для сотрудников).

Извините, это так долго. Итак, вопрос: имеет ли это смысл? Имеет ли предлагаемое решение смысл? И я делаю это слишком сложным?

Ответы [ 2 ]

15 голосов
/ 09 декабря 2008

Ну, я считаю, что я сделаю удар по своему собственному вопросу сейчас, когда я провел несколько часов, играя с различными подходами.

Мой первый подход состоял в том, чтобы настроить аутентификацию на основе сертификатов между службой 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 и ведение журналов для целей аудита теперь совсем несложно.

Надеюсь, это поможет бедной душе в будущем.

0 голосов
/ 02 апреля 2009

Для анонимного публичного доступа используйте basichttpbinding, а также укажите в файле web.config следующее

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...