Пользовательский сертификат клиента и проверка имени пользователя в службе WCF - PullRequest
7 голосов
/ 05 июля 2011

Моя конкретная проблема примерно такая:

  • В настоящее время мы работаем с набором сервисов, который требует от клиентов предоставления имени пользователя и пароля в качестве аутентификации при вызове сервисов.

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

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

Чтобы решить эту проблему, я пытаюсь реализовать собственный валидатор как для аутентификации имени пользователя / пароля (используя UserNamePasswordValidator), так и для клиентских сертификатов (используя X509CertificateValidator) в WCF. Валидатор имени пользователя / пароля проверит эти учетные данные в нашей базе данных, в то время как валидатор сертификата клиента проверит, поступил ли запрос от клиента, от которого нам требуется сертификат, и в этом случае удостоверится в наличии действительного сертификата клиента. Я не смог настроить WCF так, чтобы он использовал оба этих валидатора.

Моя конфигурация WCF на сервере в настоящее время настроена так:

<behaviors>
  <serviceBehaviors>
    <behavior name="MyServiceBehavior">
      <serviceMetadata httpsGetEnabled="true" policyVersion="Policy15" />
      <serviceDebug includeExceptionDetailInFaults="true" />
      <serviceCredentials>
        <clientCertificate>
          <authentication customCertificateValidatorType="MyWS.Security.MyServicesCertificateValidator, MyWS"
            certificateValidationMode="Custom" revocationMode="NoCheck" />
        </clientCertificate>
        <userNameAuthentication userNamePasswordValidationMode="Custom"
          customUserNamePasswordValidatorType="MyWS.Security.MyServicesUsernameValidator, MyWS" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
<bindings>
  <basicHttpBinding>
    <binding name="MySoapBinding">
      <security mode="TransportWithMessageCredential">
        <transport clientCredentialType="Certificate" />
        <message clientCredentialType="UserName" />
      </security>
    </binding>
  </basicHttpBinding>
</bindings>
<services>
  <service behaviorConfiguration="MyServiceBehavior" name="MyWS.Services.TheService">
    <endpoint address="" binding="basicHttpBinding" bindingConfiguration="MySoapBinding" name="TheService" bindingNamespace="https://services.my/TheService" contract="MyWS.Interfaces.Service.ITheService" />
    <host>
      <baseAddresses>
        <add baseAddress="https://localhost:4434/MyWS/TheService"/>
      </baseAddresses>
    </host>
  </service>
</services>

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

Я внедрил инспектор сообщений и мог бы каким-то образом решить проблему с помощью OperationContext (как предлагается в приведенной ниже ссылке), но я пока не смог найти способ сделать это таким образом ...

http://social.msdn.microsoft.com/Forums/en/wcf/thread/b6ab8b58-516b-41d4-bb0e-75b4baf92716

Полагаю, я мог бы попытаться реализовать что-то, несовместимое с тем, как работает WCF, но если у кого-то есть представление о том, как это можно исправить, я был бы рад получить ваши отзывы об этом!

Ответы [ 2 ]

6 голосов
/ 14 июля 2011

Я думаю, что нашел решение моей проблемы сейчас благодаря ценному вкладу @ ladislav-mrnka в его ответе. Я понял, что необходимо предоставить две конечные точки для настройки различных требований, а также узнал о возможностях поддержки токенов при настройке служб.

Я нашел ссылку о поддерживающих токенах на MSDN, и, следуя этому рецепту, я реализовал конечную точку на сервере со следующим настраиваемым связыванием (я переключился на настройку через код. Не уверен, что это может быть также настроенным в web.config.)

private static Binding CreateMultiFactorAuthenticationBinding()
{
    var httpsTransport = new HttpsTransportBindingElement();

    // The message security binding element will be configured to require 2 tokens:
    // 1) A username-password encrypted with the service token
    // 2) A client certificate used to sign the message

    // Create symmetric security binding element with encrypted username-password token.
    // Symmetric key is encrypted with server certificate.
    var messageSecurity = SecurityBindingElement.CreateUserNameForCertificateBindingElement();
    messageSecurity.AllowInsecureTransport = false;

    // Require client certificate as endorsing supporting token for all requests from client to server
    var clientX509SupportingTokenParameters = new X509SecurityTokenParameters
                                                    {
                                                        InclusionMode =
                                                            SecurityTokenInclusionMode.AlwaysToRecipient
                                                    };
    messageSecurity.EndpointSupportingTokenParameters.Endorsing.Add(clientX509SupportingTokenParameters);

    return new CustomBinding(messageSecurity, httpsTransport);
}

Эта привязка создает SymmetricSecurityBindingElement , где симметричный ключ (зашифрованный с помощью сертификата сервера) используется для шифрования токена имени пользователя / пароля в заголовке сообщения и самого тела сообщения.

Кроме того, маркер безопасности X509 добавлен в качестве подтверждающего, поддерживающего токен для привязки. Этот токен настроен на постоянное включение в клиентские запросы к серверу.

Эта пользовательская привязка впоследствии использовалась для настройки новой WCF-службы с конечной точкой, требующей этой привязки. Я использую WcfFacility в Castle Windsor для настройки службы.

Этот код выполняет следующие действия:

  • Устанавливает сертификат обслуживания
  • Устанавливает режим проверки для сертификатов клиента для цепочки доверия, так что входящие сертификаты клиента должны выдаваться доверенным корневым центром сертификации в хранилище сервера
  • Добавляет пользовательские валидаторы для имени пользователя / пароля и сертификата клиента.
//// Registering WCF-services
var returnFaults = new ServiceDebugBehavior {IncludeExceptionDetailInFaults = true};
var metaData = new ServiceMetadataBehavior {HttpsGetEnabled = true};

var serviceCredentials = new ServiceCredentials();

// Configure service sertificate
serviceCredentials.ServiceCertificate.SetCertificate(
    StoreLocation.LocalMachine, 
    StoreName.My, 
    X509FindType.FindBySubjectName,
    "ServerCertificate");

// Configure client certificate authentication mode
serviceCredentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.ChainTrust;

// Add custom username-password validator
serviceCredentials.UserNameAuthentication.UserNamePasswordValidationMode =
    UserNamePasswordValidationMode.Custom;
serviceCredentials.UserNameAuthentication.CustomUserNamePasswordValidator =
    _container.Resolve<MyServicesUsernameValidator>();

// Add custom certificate validator
serviceCredentials.ClientCertificate.Authentication.CertificateValidationMode =
    X509CertificateValidationMode.Custom;
serviceCredentials.ClientCertificate.Authentication.CustomCertificateValidator =
    _container.Resolve<MyServicesCertificateValidator>();

var serviceModel = new DefaultServiceModel();

serviceModel.AddEndpoints(
    WcfEndpoint.ForContract<IMyContract>().BoundTo(CreateMultiFactorAuthenticationBinding()));
serviceModel.BaseAddresses.Add(new Uri("https://server.com/MyServiceImplementation.svc"));

serviceModel.AddExtensions(serviceCredentials);
serviceModel.AddExtensions(metaData);

_container.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero)
    .Register(Component.For<IMyContract>()
                    .ImplementedBy<MyServiceImplementation>()
                    .AsWcfService(serviceModel),
                Component.For<IServiceBehavior>().Instance(returnFaults));

MyServicesUsernameValidator наследует UserNamePasswordValidator, а MyServicesCertificateValidator наследует X509CertificateValidator. Оба переопределяют свои соответствующие методы Validate.

Кажется, это решает мою конкретную проблему ... Надеюсь, она решит вашу! :)

4 голосов
/ 05 июля 2011

Это невозможно определить в конфигурации с привязками из коробки. Даже пользовательская привязка не поддерживает достаточно инфраструктуры для определения такой привязки в конфигурации.

Сначала вам определенно понадобятся две конечные точки для этого. Один будет использоваться для клиентов только с именем пользователя и паролем. Эта конечная точка может быть настроена с некоторой общей привязкой, ожидающей либо безопасность сообщений с учетными данными клиента UserName, либо безопасность транспорта с учетными данными сообщений. Вторая конечная точка будет для вашей более сложной проверки. Эта конечная точка нуждается в новой привязке, определенной в коде. Эта привязка должна использовать:

  • Асимметричный элемент привязки безопасности (взаимная проверка подлинности сертификата)
  • X.509 токен безопасности как основной токен безопасности
  • токен безопасности имени пользователя как поддерживающий токен безопасности

Это пример привязки, которую я должен был использовать при связи с аналогичным сервисом:

  Custom binding = new CustomBinding();
  var userNameToken = new UserNameSecurityTokenParameters();
  userNameToken.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;

  var securityElement = new AsymmetricSecurityBindingElement();
  securityElement.IncludeTimestamp = true;
  securityElement.RecipientTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.SubjectKeyIdentifier, SecurityTokenInclusionMode.Never);
  securityElement.InitiatorTokenParameters = new X509SecurityTokenParameters(X509KeyIdentifierClauseType.SubjectKeyIdentifier, SecurityTokenInclusionMode.AlwaysToRecipient);
  securityElement.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256;
  securityElement.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
  securityElement.SetKeyDerivation(false);
  securityElement.EndpointSupportingTokenParameters.SignedEncrypted.Add(userNameToken);
  securityElement.MessageProtectionOrder = MessageProtectionOrder.EncryptBeforeSign;
  securityElement.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11;
  binding.Elements.Add(securityElement);

  var encodingElement = new TextMessageEncodingBindingElement();
  encodingElement.MessageVersion = MessageVersion.Soap12WSAddressingAugust2004;
  binding.Elements.Add(encodingElement);

  var httpElement = new HttpTransportBindingElement();
  httpElement.UseDefaultWebProxy = true;
  binding.Elements.Add(httpElement); 

В этом примере используется CustomBinding, определенный в коде. Если вы хотите использовать это в конфигурации, вы должны создать полностью новое связывание и расширение связывания и зарегистрировать это расширение в файле конфигурации.

Даже тогда я не уверен, что будут использоваться оба валидатора - я использовал это как клиент сервиса. Суть в том, что запрос может иметь только один основной токен, и возможно, что инфраструктура WCF по умолчанию выберет только один для проверки, но такую ​​логику также можно заменить.

...