WCF 下的 SOAP Web 服务的同时客户端证书和用户名身份验证
Simultaneous client-certificate and username authentication of a SOAP web service under WCF
我无法通过 SOAP Web 服务进行身份验证,这需要使用客户端证书和用户名同时进行身份验证。
目标服务基本上支持三种身份验证模式:
- 用户名 + 密码——这对我很有效,使用
BasicHttpsSecurityMode.Transport
- 仅证书 — 由于各种原因无法使用此模式
- 证书 + 用户名 —(请注意,在这种情况下没有密码,留空)我无法使用此模式。
在每种情况下,都是使用 TLS 1.2 的 HTTPS。
事实上,我不知道如何配置绑定和身份验证设置来描述 WCF 中的这种身份验证模式。到目前为止,我尝试了各种 BasicHttpsBinding
配置和 CustomBinding
设置,包括 this answer.
中描述的设置
在下面的代码中,当同时指定 endpointConfig.Username
和 endpointConfig.ClientCertificate
时,我根据能够 google 的示例构建了一个 CustomBinding
,但它赢了工作。客户端确实向服务器发送了正确的证书,服务器用 Change Cipher Spec 消息回复,客户端终止了 TLS 连接。
我最多能得到以下错误信息:
Could not establish trust relationship for the SSL/TLS secure channel with authority 'ws1c.czebox.cz'.
Could not establish secure channel for SSL/TLS with authority 'ws1c.czebox.cz'.
构建服务客户端的代码如下所示:
// prepare binding
Binding binding;
// NOTE A username alone is sufficient for Basic authentication e.g. when logging-in to a DataBox using the HostCert method
if (!String.IsNullOrEmpty(endpointConfig.Username) && endpointConfig.ClientCertificate != null)
{
// client certificate and username + password
// the below code is experimental and just doesn't work (yields the first error mentioned above)
var tb = new HttpsTransportBindingElement();
tb.MaxReceivedMessageSize = MaxSupportedMessageSize;
tb.RequireClientCertificate = true;
tb.AuthenticationScheme = AuthenticationSchemes.Basic;
var ub = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
binding = new CustomBinding(ub, tb);
}
else if (!String.IsNullOrEmpty(endpointConfig.Username))
{
// username + password
var b = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
b.MaxReceivedMessageSize = MaxSupportedMessageSize;
b.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding = b;
}
else if (endpointConfig.ClientCertificate != null)
{
// client certificate
var b = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
b.MaxReceivedMessageSize = MaxSupportedMessageSize;
b.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding = b;
}
else
{
throw new NotSupportedException();
}
// prepare endpoint identity
EndpointIdentity identity = null;
X509Certificate2 cert = null;
if (endpointConfig.ClientCertificate != null)
{
var clientCredentials = new ClientCredentials();
clientCredentials.ClientCertificate.SetCertificate(
endpointConfig.ClientCertificate.StoreLocation,
endpointConfig.ClientCertificate.StoreName,
endpointConfig.ClientCertificate.FindBy,
endpointConfig.ClientCertificate.FindValue);
cert = clientCredentials.ClientCertificate.Certificate;
identity = EndpointIdentity.CreateX509CertificateIdentity(cert);
}
// prepare endpoint address
var address = new EndpointAddress(new Uri(endpointConfig.EndpointUrl), identity);
// create connector
var connector = new dmInfoPortTypeClient(binding, address);
// setup username + password authentication
// NOTE Password may be empty when logging-in to a DataBox using the HostCert method
if (!String.IsNullOrEmpty(endpointConfig.Username))
{
connector.ClientCredentials.UserName.UserName = endpointConfig.Username;
connector.ClientCredentials.UserName.Password = endpointConfig.Password;
}
// setup client-certificate authentication
if (endpointConfig.ClientCertificate != null)
{
connector.ClientCredentials.ClientCertificate.Certificate = cert;
}
请注意,我对服务器的控制为零。是政府服务,没法商量。
运行 在 .NET 4.7.x 上。可以升级到 4.8,但不能迁移到 .NET Core。
(根据广泛的调试和诊断得出的解决方案进行自我回答。欢迎提供更好的答案。)
原来情况和this Q&A pair描述的差不多。但是,在我的例子中,我以编程方式构建 WCF 端点,避免了 XML 配置。
1) 正确的自定义绑定
对于证书 + 用户名选项,以下代码构造了一个有效的自定义绑定:
// HTTPS transport with basic authentication and required client certificate
// note that AuthenticationSchemes.Certificate doesn't work, the server requires
// Basic authentication
var tb = new HttpsTransportBindingElement();
tb.MaxReceivedMessageSize = MaxSupportedMessageSize;
tb.RequireClientCertificate = true;
tb.AuthenticationScheme = AuthenticationSchemes.Basic;
// encoding: the server uses SOAP 1.1. which results in the MIME content-type text/xml
// note that by default, the encoding is SOAP 1.2, which implies the content-type
// application/soap+xml not compatible with the server, resulting in an exception
var enc = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
// transport security: pass username over transport; it is necessary to allow
// unsecured responses; don't know why, but without this setting it won't work
var ub = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
ub.EnableUnsecuredResponse = true;
// construct the binding
binding = new CustomBinding(enc, ub, tb);
注意:这是两个方面,IMO,根据我的具体情况定制,因此不适合通用解决方案:
- 使用显式编码表示
MessageVersion.Soap11
用于 SOAP 1.1;默认情况下,SOAP 1.2 的编码是 MessageVersion.Soap12
- 使用
SecurityBindingElement.EnableUnsecuredResponse
启用不安全响应
2) 没有端点身份
用 // prepare endpoint identity
注释的代码部分是错误的,错误地使用 客户端 证书作为 WCF 应该期望从 服务器.
3) WCF 跟踪
诊断问题时,启用 WCF 跟踪的功能很有帮助。可以在 app.config
中使用以下内容来启用日志记录:
<system.diagnostics>
<!-- use for WCF diagnostis: WCF tracing and message logging -->
<sources>
<source name="System.ServiceModel"
switchValue="Information, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="traceListener"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData= "D:\Logs\WCF-Traces.svclog" />
</listeners>
</source>
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add name="messages"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="D:\Logs\WCF-Messages.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
我无法通过 SOAP Web 服务进行身份验证,这需要使用客户端证书和用户名同时进行身份验证。
目标服务基本上支持三种身份验证模式:
- 用户名 + 密码——这对我很有效,使用
BasicHttpsSecurityMode.Transport
- 仅证书 — 由于各种原因无法使用此模式
- 证书 + 用户名 —(请注意,在这种情况下没有密码,留空)我无法使用此模式。
在每种情况下,都是使用 TLS 1.2 的 HTTPS。
事实上,我不知道如何配置绑定和身份验证设置来描述 WCF 中的这种身份验证模式。到目前为止,我尝试了各种 BasicHttpsBinding
配置和 CustomBinding
设置,包括 this answer.
在下面的代码中,当同时指定 endpointConfig.Username
和 endpointConfig.ClientCertificate
时,我根据能够 google 的示例构建了一个 CustomBinding
,但它赢了工作。客户端确实向服务器发送了正确的证书,服务器用 Change Cipher Spec 消息回复,客户端终止了 TLS 连接。
我最多能得到以下错误信息:
Could not establish trust relationship for the SSL/TLS secure channel with authority 'ws1c.czebox.cz'.
Could not establish secure channel for SSL/TLS with authority 'ws1c.czebox.cz'.
构建服务客户端的代码如下所示:
// prepare binding
Binding binding;
// NOTE A username alone is sufficient for Basic authentication e.g. when logging-in to a DataBox using the HostCert method
if (!String.IsNullOrEmpty(endpointConfig.Username) && endpointConfig.ClientCertificate != null)
{
// client certificate and username + password
// the below code is experimental and just doesn't work (yields the first error mentioned above)
var tb = new HttpsTransportBindingElement();
tb.MaxReceivedMessageSize = MaxSupportedMessageSize;
tb.RequireClientCertificate = true;
tb.AuthenticationScheme = AuthenticationSchemes.Basic;
var ub = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
binding = new CustomBinding(ub, tb);
}
else if (!String.IsNullOrEmpty(endpointConfig.Username))
{
// username + password
var b = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
b.MaxReceivedMessageSize = MaxSupportedMessageSize;
b.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding = b;
}
else if (endpointConfig.ClientCertificate != null)
{
// client certificate
var b = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
b.MaxReceivedMessageSize = MaxSupportedMessageSize;
b.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding = b;
}
else
{
throw new NotSupportedException();
}
// prepare endpoint identity
EndpointIdentity identity = null;
X509Certificate2 cert = null;
if (endpointConfig.ClientCertificate != null)
{
var clientCredentials = new ClientCredentials();
clientCredentials.ClientCertificate.SetCertificate(
endpointConfig.ClientCertificate.StoreLocation,
endpointConfig.ClientCertificate.StoreName,
endpointConfig.ClientCertificate.FindBy,
endpointConfig.ClientCertificate.FindValue);
cert = clientCredentials.ClientCertificate.Certificate;
identity = EndpointIdentity.CreateX509CertificateIdentity(cert);
}
// prepare endpoint address
var address = new EndpointAddress(new Uri(endpointConfig.EndpointUrl), identity);
// create connector
var connector = new dmInfoPortTypeClient(binding, address);
// setup username + password authentication
// NOTE Password may be empty when logging-in to a DataBox using the HostCert method
if (!String.IsNullOrEmpty(endpointConfig.Username))
{
connector.ClientCredentials.UserName.UserName = endpointConfig.Username;
connector.ClientCredentials.UserName.Password = endpointConfig.Password;
}
// setup client-certificate authentication
if (endpointConfig.ClientCertificate != null)
{
connector.ClientCredentials.ClientCertificate.Certificate = cert;
}
请注意,我对服务器的控制为零。是政府服务,没法商量。
运行 在 .NET 4.7.x 上。可以升级到 4.8,但不能迁移到 .NET Core。
(根据广泛的调试和诊断得出的解决方案进行自我回答。欢迎提供更好的答案。)
原来情况和this Q&A pair描述的差不多。但是,在我的例子中,我以编程方式构建 WCF 端点,避免了 XML 配置。
1) 正确的自定义绑定
对于证书 + 用户名选项,以下代码构造了一个有效的自定义绑定:
// HTTPS transport with basic authentication and required client certificate
// note that AuthenticationSchemes.Certificate doesn't work, the server requires
// Basic authentication
var tb = new HttpsTransportBindingElement();
tb.MaxReceivedMessageSize = MaxSupportedMessageSize;
tb.RequireClientCertificate = true;
tb.AuthenticationScheme = AuthenticationSchemes.Basic;
// encoding: the server uses SOAP 1.1. which results in the MIME content-type text/xml
// note that by default, the encoding is SOAP 1.2, which implies the content-type
// application/soap+xml not compatible with the server, resulting in an exception
var enc = new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8);
// transport security: pass username over transport; it is necessary to allow
// unsecured responses; don't know why, but without this setting it won't work
var ub = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
ub.EnableUnsecuredResponse = true;
// construct the binding
binding = new CustomBinding(enc, ub, tb);
注意:这是两个方面,IMO,根据我的具体情况定制,因此不适合通用解决方案:
- 使用显式编码表示
MessageVersion.Soap11
用于 SOAP 1.1;默认情况下,SOAP 1.2 的编码是MessageVersion.Soap12
- 使用
SecurityBindingElement.EnableUnsecuredResponse
启用不安全响应
2) 没有端点身份
用 // prepare endpoint identity
注释的代码部分是错误的,错误地使用 客户端 证书作为 WCF 应该期望从 服务器.
3) WCF 跟踪
诊断问题时,启用 WCF 跟踪的功能很有帮助。可以在 app.config
中使用以下内容来启用日志记录:
<system.diagnostics>
<!-- use for WCF diagnostis: WCF tracing and message logging -->
<sources>
<source name="System.ServiceModel"
switchValue="Information, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="traceListener"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData= "D:\Logs\WCF-Traces.svclog" />
</listeners>
</source>
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add name="messages"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="D:\Logs\WCF-Messages.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>