[英]Custom client certificate and username validation in WCF service
我的特殊问题是这样的:
我们目前正在运行一组服务,要求客户端在调用服务时提供用户名和密码作为身份验证。
我们希望在这些服务上实施 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 之前检查证书),但我看不出我如何能够结合customCertificateValidatorType
和customUserNamePasswordValidatorType
作为消息层的凭证类型。
我已经实现了一个消息检查器,并且可能能够以某种方式使用OperationContext
解决问题(如下面的链接中所建议的那样),但我还没有找到一种方法让我这样做。
http://social.msdn.microsoft.com/Forums/en/wcf/thread/b6ab8b58-516b-41d4-bb0e-75b4baf92716
我想我可能正在尝试实现与 WCF 的工作方式不兼容的东西,但如果有人知道如何解决这个问题,我会很高兴收到您的反馈。
我想我现在已经找到了解决我的问题的方法,这要归功于@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 ,其中使用对称密钥(使用服务器证书加密)来加密消息 header 和消息正文中的用户名/密码安全令牌。
此外,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 方法。
这似乎解决了我的特殊问题......希望它能解决你的问题::)
使用开箱即用的绑定无法在配置中定义。 即使自定义绑定也不支持足够的基础架构来在配置中定义此类绑定。
首先,您肯定需要两个端点。 一个将仅用于具有用户名/密码的客户端。 此端点可以配置一些通用绑定,期望使用 UserName 客户端凭据的消息安全性或使用消息凭据的传输安全性。 第二个端点将用于您更复杂的验证。 该端点需要在代码中定义新的绑定。 此绑定必须使用:
这是我在与类似服务通信时必须使用的绑定示例:
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 基础设施可能只会选择一个来验证,但也可以替换这种逻辑。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.