WCF 服务中的自定义客户端证书和用户名验证

Posted

技术标签:

【中文标题】WCF 服务中的自定义客户端证书和用户名验证【英文标题】:Custom client certificate and username validation in WCF service 【发布时间】:2011-09-28 19:42:42 【问题描述】:

我的特殊问题是这样的:

我们目前正在运行一组服务,要求客户端在调用服务时提供用户名和密码作为身份验证。

我们希望在这些服务上实施 PKI 基础架构,但我们的一些合作伙伴将花费比其他合作伙伴更长的时间来适应这种新的基础架构。

作为第一步,我们需要一些合作伙伴提供客户证书。需要客户端证书(除了用户名和密码)来访问他们在我们服务器上的数据,而其他用户只需要用户名和密码。

为了解决这个问题,我尝试在 WCF 中为用户名/密码身份验证(使用 UserNamePasswordValidator)和客户端证书(使用 X509CertificateValidator)实现自定义验证器。用户名/密码验证器将向我们的数据库验证这些凭据,而客户端证书验证器将检查请求是否来自我们需要证书的客户端,如果是,则验证是否提供了有效的客户端证书。我无法配置 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 之前检查证书),但我看不到如何组合customCertificateValidatorTypecustomUserNamePasswordValidatorType 都是消息层的凭据类型。

我已经实现了一个消息检查器,并且可能能够以某种方式使用OperationContext 来解决问题(如下面的链接中所建议的那样),但我还没有找到让我这样做的方法还没有。

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

我想我可能正在尝试实现与 WCF 工作方式不兼容的东西,但如果有人知道如何解决这个问题,我很高兴收到您的反馈。

【问题讨论】:

【参考方案1】:

感谢@ladislav-mrnka 在他的回答中提供的宝贵意见,我想我现在找到了解决问题的方法。我意识到有必要提供两个端点来配置不同的需求,并且我还了解了配置服务时支持令牌的可能性。

我在 MSDN 上找到了一个关于 supporting tokens 的链接,通过遵循这个配方,我已经使用以下自定义绑定在服务器上实现了端点(我通过代码切换到配置。不确定这是否可以在 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 服务。我正在使用 Castle Windsor 中的 WcfFacility 来配置服务。

此代码执行以下操作:

设置服务证书 将客户端证书的验证模式设置为链式信任,因此传入的客户端证书必须由服务器存储中受信任的根证书颁发机构颁发 为用户名/密码凭据和客户端证书添加自定义验证器
//// 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 方法。

这似乎解决了我的特殊问题......希望它能解决你的问题! :)

【讨论】:

【参考方案2】:

这是不可能在配置中使用开箱即用的绑定来定义的。即使自定义绑定也不支持足够的基础架构来在配置中定义此类绑定。

首先,您肯定需要两个端点。一个将仅用于具有用户名/密码的客户端。此端点可以配置一些常见的绑定,期望使用 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 基础结构可能只会选择一个来验证,但也可以替换这种逻辑。

【讨论】:

感谢您的评论。它引导我朝着正确的方向前进,在自定义绑定中使用多个端点并支持令牌。

以上是关于WCF 服务中的自定义客户端证书和用户名验证的主要内容,如果未能解决你的问题,请参考以下文章

WCF 客户端证书验证 + Windows 身份验证

WCF 消息安全证书

WCF SOAP 1.1 和 WS-Security 1.0、客户端证书传输身份验证、消息正文签名的服务证书、用户名令牌、密码摘要、随机数

WCF 服务 - 具有用户名身份验证的证书和消息安全性

WCF 安全性之 自定义用户名密码验证

如何验证和授权每个 WCF 调用?