Подтвердить что ты не робот

Веб-сайт ASP.NET + приложение Windows Forms + служба WCF: учетные данные клиента

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

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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Мой первый подход заключался в том, чтобы настроить аутентификацию на основе сертификатов между сервисом WCF и общедоступным веб-сайтом (веб-сайт является клиентом/клиентом службы). Несколько тестовых сертификатов, сгенерированных с помощью makecert, вставляют их в теги Personal, Trusted People и Trusted Root Certification Authorities (потому что я не мог беспокоиться о том, чтобы генерировать реальные данные в отношении наших служб сертификатов домена), некоторые модификации файлов конфигурации, и здорово, мы все настроены.

Чтобы веб-сайт не мог поддерживать имя пользователя и пароль для пользователей, идея заключается в том, что после входа пользователя на веб-сайт с помощью проверки подлинности с помощью форм веб-сайт может передавать только имя пользователя (доступное через HttpContext.Current.User.Identity.Name) как необязательный UserNameSecurityToken в дополнение к X509CertificateSecurityToken, который фактически используется для защиты сообщения. Если обнаружен опциональный токен имени пользователя, то служба WCF скажет: "Эй, эта доверенная подсистема говорит, что этот пользователь правильно аутентифицирован, поэтому позвольте мне настроить MyCustomPrincipal для этого пользователя и установить его в текущем потоке, чтобы фактический код службы может проверить это". Если это не так, то будет установлена ​​анонимная версия MyCustomPrincipal.

Так что я пошел на пять часов, пытаясь реализовать это, и с помощью различных blogs, я смог это сделать. (Я потратил большую часть своего времени на отладку проблемы, когда у меня была всякая конфигурация и поддерживался класс, и после этого я установил свои пользовательские полномочия после того, как я запустил хост, а не раньше, поэтому ни одно из моих усилий не вступало в силу. компьютеры.) У меня был TrustedSubsystemAuthorizationPolicy, который проверил проверку сертификата X509, установив анонимный MyCustomPrincipal, TrustedSubsystemImpersonationAuthorizationPolicy, который принял токен имени пользователя с пустым паролем и установил клиентскую роль MyCustomPrincipal, если увидел, что анонимная доверенная подсистема, и UserNameAuthorizationPolicy, которая провела регулярную проверку имени пользователя и пароля для других конечных точек, где сертификаты X509 не используются. Это сработало, и это было замечательно.

Но.

В тот момент, когда я играл с созданным клиентским прокси-кодом, веб-сайт использовал бы для общения с этой услугой. Указание UserName в свойстве ClientCredentials сгенерированного объекта ClientBase<T> было достаточно простым. Но главная проблема заключается в том, что учетные данные специфичны для ChannelFactory, а не для конкретного вызова метода.

Вы видите, что новый() подключение к клиенту WCF-клиента более дорогое, чем вы можете подумать. Я написал быстрое и грязное приложение, чтобы проверить производительность самостоятельно: и новый(), запустивший новый прокси, и вызов метода десять раз занял около 6 секунд, тогда как новый() подключился к прокси-серверу один раз и вызвал только метод в 10 раз около 3/5-й секунды. Это всего лишь удручающая разница в производительности.

Итак, я могу просто реализовать пул или кеш для клиентского прокси, не так ли? Ну, нет, он нелегко работал: информация учетных данных клиента находится на уровне канала factory, потому что он может использоваться для защиты транспорта, а не только от сообщения, а некоторые привязки сохраняют фактический транспорт между вызовами службы. Поскольку учетные данные клиента уникальны для прокси-объекта, это означает, что мне нужно будет иметь уникальный кешированный экземпляр для каждого пользователя, находящегося на веб-сайте. Это потенциально много прокси-объектов, сидящих в памяти, и довольно @# [email protected]# близко к проблеме, которую я пытался избежать в первую очередь! И так как мне нужно прикоснуться к свойству Endpoint, чтобы настроить привязку для необязательного поддерживающего токена имени пользователя, я не могу воспользоваться автоматическим каналом factory кэширование, что Microsoft добавила "бесплатно" в .NET 3.5.

Вернемся к чертежной доске: мой второй подход и тот, который, как мне кажется, я сейчас использую, должен придерживаться безопасности сертификата X509 между клиентским веб-сайтом и службой WCF. Я просто отправлю пользовательский заголовок SOA-заголовка "UserName" в своих сообщениях, и служба WCF может проверить этот заголовок SOAP, определить, исходит ли она из доверенной подсистемы, такой как веб-сайт, и если да, установите MyCustomPrincipal в аналогично предыдущему.

Codeproject и случайные люди в Googleэто чудесные вещи, потому что они помогли мне быстро и быстро запустить это, даже после того, как запутался в странной ошибке WCF, когда дело доходит до пользовательских настроек конечных точек в конфигурации. Внедряя инспекторов сообщений на стороне клиента и стороне службы - один для добавления заголовка UserName, а другой для его чтения и установки правильного принципала - этот код находится в одном месте, где я могу просто забыть об этом. Поскольку мне не нужно прикасаться к свойству Endpoint, я получаю встроенное кэширование factory в качестве бесплатного. А поскольку 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, "[email protected]"
    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 и регистрация для целей аудита теперь оснащена.

Я надеюсь, что это поможет некоторым бедным душам в будущем.

Ответ 2

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