使用代码隐藏而不是 Web.Config 端点调用服务时出现不安全或不正确保护故障错误
Unsecured or incorrectly secured fault error when calling a service with code-behind instead of Web.Config endpoint
我有一个具有消息安全性的 WCF 服务,我正在尝试将客户端连接到它。
使用 "Add Service Reference" 在 Visual Studio 中自动生成的默认 Web.Config 设置,连接效果惊人,设置如下:
端点:
<endpoint address="http://localhost:56017/services/MaternumPdfService.svc/soap"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IMaternumPdfService"
contract="MaternumPdfServiceReference.IMaternumPdfService" name="WSHttpBinding_IMaternumPdfService">
<identity>
<certificate encodedValue="BASE64 IS HERE" />
</identity>
</endpoint>
绑定:
<binding name="WSHttpBinding_IMaternumPdfService" maxReceivedMessageSize="2147483647">
<security>
<message clientCredentialType="UserName" negotiateServiceCredential="false"
establishSecurityContext="false" />
</security>
<readerQuotas maxStringContentLength="2147483647" maxDepth="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
</binding>
这工作顺利:
using (MaternumPdfServiceClient pdfService = new MaternumPdfServiceClient("WSHttpBinding_IMaternumPdfService"))
{
pdfService.ClientCredentials.UserName.UserName = "sample";
pdfService.ClientCredentials.UserName.Password = "sample";
pdfService.Open();
if (!pdfService.State.Equals(CommunicationState.Opened))
{
return null;
}
MemoryStream ms = new MemoryStream();
pdfService.GetMaternumPdf(pdftype,params).CopyTo(ms);
}
但是,尝试在没有这些设置的情况下构建连接失败。以下是我建立连接的方式:
X509Store store = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 cert = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, $"CN={CN}", false)[0];
store.Close();
Uri mpsUri = new Uri("http://localhost:56017/services/MaternumPdfService.svc/soap");
X509CertificateEndpointIdentity mpsIdentity = new X509CertificateEndpointIdentity(cert);
EndpointAddress mpsEndpoint = new EndpointAddress(mpsUri, mpsIdentity);
WSHttpBinding mpsBinding = new WSHttpBinding()
{
Name = "WSHttpBinding_IMaternumPdfService",
MaxReceivedMessageSize = Int32.MaxValue
};
//mpsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
mpsBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
mpsBinding.Security.Message.NegotiateServiceCredential = false;
mpsBinding.Security.Message.EstablishSecurityContext = false;
mpsBinding.ReaderQuotas.MaxDepth = Int32.MaxValue;
mpsBinding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
mpsBinding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
mpsBinding.ReaderQuotas.MaxBytesPerRead = Int32.MaxValue;
mpsBinding.ReaderQuotas.MaxNameTableCharCount = Int32.MaxValue;
using (MaternumPdfServiceClient pdfService = new MaternumPdfServiceClient(mpsBinding, mpsEndpoint))
{
pdfService.ClientCredentials.UserName.UserName = "sample";
pdfService.ClientCredentials.UserName.Password = "sample";
pdfService.ClientCredentials.ClientCertificate.Certificate = cert;
pdfService.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
pdfService.ClientCredentials.ServiceCertificate.DefaultCertificate = GetCertificate("MaternumCertificateClient");
pdfService.Endpoint.Contract.Name = "MaternumPdfServiceReference.IMaternumPdfService";
pdfService.Endpoint.Name = "EndpointBehaviorWithAuthentication";
pdfService.Open();
if (!pdfService.State.Equals(CommunicationState.Opened))
{
return null;
}
MemoryStream ms = new MemoryStream();
//Next line throws the exception
pdfService.GetMaternumPdf(myPdf.PdfType, myPdf.JsonParameters).CopyTo(ms);
}
我以为我在 C# 版本和使用 Web.Config 设置的版本之间构建完全等效的代码,但与第二个版本的连接导致我出现异常:
'An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.'
有以下 InnerException:
An error occurred when verifying security for the message.
不是特别清楚...这似乎与消息安全有关,但与 Web.Config 的连接工作得非常好,这让我相信我没有正确设置证书 class.
我调查过:
WCF gives an unsecured or incorrectly secured fault error
- Server和Client都是localhost,应该不是时间不匹配
- 凭据正确,因为 Web.Config 版本使用相同的凭据并验证正常
- 我使用的是 HTTP,而不是 HTTPS
- 使用 IISExpress,而不是 IIS
就是这样。我想我在错误的地方设置了客户端证书,如果不是通过 ClientCredentials.ClientCertificate.Certificate 或端点的身份,我应该如何设置它?
...通常情况下,我的配置有误。我得到了一个名为 "MaternumCertificateClient" 的证书,而不是我的服务器配置的证书,即 "MaternumCertificateServer".
更重要的是我是如何得到这个错误的。
第一步,我设置 Wireshark 以查看服务器回复的内容:
HTTP/1.1 500 Internal Server Error
Cache-Control: private
Content-Type: application/soap+xml; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ASP.NET_SessionId=k0xmopx3eitnvmocv1rjas4h; path=/; HttpOnly
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcREVWXE1BVFxNYXRlcm51bV9hcHBcc2VydmljZXNcTWF0ZXJudW1QZGZTZXJ2aWNlLnN2Y1xzb2Fw?=
X-Powered-By: ASP.NET
Date: Thu, 22 Aug 2019 16:25:19 GMT
Content-Length: 648
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
<a:RelatesTo>urn:uuid:uid is here</a:RelatesTo>
</s:Header>
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
<s:Subcode>
<s:Value xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">a:InvalidSecurity</s:Value>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-GB">An error occurred when verifying security for the message.</s:Text>
</s:Reason>
</s:Fault>
</s:Body>
</s:Envelope>
并没有好多少,但我至少知道是谁在抛出异常。所以,如果我能找到更详细的信息,我会查找。我遇到了这个:An error occurred when verifying security for the message
因此,如图所示,我将我的服务器设置为:
<serviceSecurityAudit auditLogLocation=“Application“
serviceAuthorizationAuditLevel=“Failure“
messageAuthenticationAuditLevel=“Failure“
suppressAuditFailure=“true“ />
在 configuration/system.serviceModel/behaviors/serviceBehaviors/behavior.
然后,Windows 事件查看器有错误的详细信息。
Windows Event Viewer details
显示的消息,
MessageSecurityException: The EncryptedKey clause was not wrapped with the required encryption token 'System.IdentityModel.Tokens.X509SecurityToken'.
表示证书不匹配。我正在加载一个名为 MaternumCertificateClient 的客户端,我需要 MaternumCertificateServer。
此外,行
pdfService.ClientCredentials.ClientCertificate.Certificate = cert;
pdfService.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
pdfService.ClientCredentials.ServiceCertificate.DefaultCertificate = GetCertificate("MaternumCertificateClient");
实际上不需要;证书是从端点的身份中读取的,我的设置工作不需要这些设置。
正如您所指出的,除了我们使用证书对客户端进行身份验证外,无需在客户端提供证书。比如下面的配置。
<wsHttpBinding>
<binding name="WSHttpBinding_ICalculator">
<security mode="Message">
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
请参考以下内容link。客户端提供证书来代表身份并加密通信。
https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-a-certificate-client
在服务器端的绑定配置中,只需要 username/password 凭据。
<security>
<message clientCredentialType="UserName" negotiateServiceCredential="false"
establishSecurityContext="false" />
</security>
证书通过使用 public 密钥加密 soap 消息来提供消息安全性。通信过程中,客户端和服务器端交换证书的public密钥。我们甚至可以指定服务器域名作为标识,它只是用来确保服务器的身份。
此外,如果您的回复解决了您的问题。请标记它有用,以便帮助遇到类似问题的其他人。
如果有什么我可以帮忙的,请随时告诉我。
我有一个具有消息安全性的 WCF 服务,我正在尝试将客户端连接到它。
使用 "Add Service Reference" 在 Visual Studio 中自动生成的默认 Web.Config 设置,连接效果惊人,设置如下:
端点:
<endpoint address="http://localhost:56017/services/MaternumPdfService.svc/soap"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IMaternumPdfService"
contract="MaternumPdfServiceReference.IMaternumPdfService" name="WSHttpBinding_IMaternumPdfService">
<identity>
<certificate encodedValue="BASE64 IS HERE" />
</identity>
</endpoint>
绑定:
<binding name="WSHttpBinding_IMaternumPdfService" maxReceivedMessageSize="2147483647">
<security>
<message clientCredentialType="UserName" negotiateServiceCredential="false"
establishSecurityContext="false" />
</security>
<readerQuotas maxStringContentLength="2147483647" maxDepth="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
</binding>
这工作顺利:
using (MaternumPdfServiceClient pdfService = new MaternumPdfServiceClient("WSHttpBinding_IMaternumPdfService"))
{
pdfService.ClientCredentials.UserName.UserName = "sample";
pdfService.ClientCredentials.UserName.Password = "sample";
pdfService.Open();
if (!pdfService.State.Equals(CommunicationState.Opened))
{
return null;
}
MemoryStream ms = new MemoryStream();
pdfService.GetMaternumPdf(pdftype,params).CopyTo(ms);
}
但是,尝试在没有这些设置的情况下构建连接失败。以下是我建立连接的方式:
X509Store store = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 cert = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, $"CN={CN}", false)[0];
store.Close();
Uri mpsUri = new Uri("http://localhost:56017/services/MaternumPdfService.svc/soap");
X509CertificateEndpointIdentity mpsIdentity = new X509CertificateEndpointIdentity(cert);
EndpointAddress mpsEndpoint = new EndpointAddress(mpsUri, mpsIdentity);
WSHttpBinding mpsBinding = new WSHttpBinding()
{
Name = "WSHttpBinding_IMaternumPdfService",
MaxReceivedMessageSize = Int32.MaxValue
};
//mpsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
mpsBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
mpsBinding.Security.Message.NegotiateServiceCredential = false;
mpsBinding.Security.Message.EstablishSecurityContext = false;
mpsBinding.ReaderQuotas.MaxDepth = Int32.MaxValue;
mpsBinding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
mpsBinding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
mpsBinding.ReaderQuotas.MaxBytesPerRead = Int32.MaxValue;
mpsBinding.ReaderQuotas.MaxNameTableCharCount = Int32.MaxValue;
using (MaternumPdfServiceClient pdfService = new MaternumPdfServiceClient(mpsBinding, mpsEndpoint))
{
pdfService.ClientCredentials.UserName.UserName = "sample";
pdfService.ClientCredentials.UserName.Password = "sample";
pdfService.ClientCredentials.ClientCertificate.Certificate = cert;
pdfService.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
pdfService.ClientCredentials.ServiceCertificate.DefaultCertificate = GetCertificate("MaternumCertificateClient");
pdfService.Endpoint.Contract.Name = "MaternumPdfServiceReference.IMaternumPdfService";
pdfService.Endpoint.Name = "EndpointBehaviorWithAuthentication";
pdfService.Open();
if (!pdfService.State.Equals(CommunicationState.Opened))
{
return null;
}
MemoryStream ms = new MemoryStream();
//Next line throws the exception
pdfService.GetMaternumPdf(myPdf.PdfType, myPdf.JsonParameters).CopyTo(ms);
}
我以为我在 C# 版本和使用 Web.Config 设置的版本之间构建完全等效的代码,但与第二个版本的连接导致我出现异常:
'An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.'
有以下 InnerException:
An error occurred when verifying security for the message.
不是特别清楚...这似乎与消息安全有关,但与 Web.Config 的连接工作得非常好,这让我相信我没有正确设置证书 class.
我调查过:
WCF gives an unsecured or incorrectly secured fault error
- Server和Client都是localhost,应该不是时间不匹配
- 凭据正确,因为 Web.Config 版本使用相同的凭据并验证正常
- 我使用的是 HTTP,而不是 HTTPS
- 使用 IISExpress,而不是 IIS
就是这样。我想我在错误的地方设置了客户端证书,如果不是通过 ClientCredentials.ClientCertificate.Certificate 或端点的身份,我应该如何设置它?
...通常情况下,我的配置有误。我得到了一个名为 "MaternumCertificateClient" 的证书,而不是我的服务器配置的证书,即 "MaternumCertificateServer".
更重要的是我是如何得到这个错误的。
第一步,我设置 Wireshark 以查看服务器回复的内容:
HTTP/1.1 500 Internal Server Error
Cache-Control: private
Content-Type: application/soap+xml; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ASP.NET_SessionId=k0xmopx3eitnvmocv1rjas4h; path=/; HttpOnly
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcREVWXE1BVFxNYXRlcm51bV9hcHBcc2VydmljZXNcTWF0ZXJudW1QZGZTZXJ2aWNlLnN2Y1xzb2Fw?=
X-Powered-By: ASP.NET
Date: Thu, 22 Aug 2019 16:25:19 GMT
Content-Length: 648
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
<a:RelatesTo>urn:uuid:uid is here</a:RelatesTo>
</s:Header>
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
<s:Subcode>
<s:Value xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">a:InvalidSecurity</s:Value>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-GB">An error occurred when verifying security for the message.</s:Text>
</s:Reason>
</s:Fault>
</s:Body>
</s:Envelope>
并没有好多少,但我至少知道是谁在抛出异常。所以,如果我能找到更详细的信息,我会查找。我遇到了这个:An error occurred when verifying security for the message
因此,如图所示,我将我的服务器设置为:
<serviceSecurityAudit auditLogLocation=“Application“
serviceAuthorizationAuditLevel=“Failure“
messageAuthenticationAuditLevel=“Failure“
suppressAuditFailure=“true“ />
在 configuration/system.serviceModel/behaviors/serviceBehaviors/behavior.
然后,Windows 事件查看器有错误的详细信息。
Windows Event Viewer details
显示的消息,
MessageSecurityException: The EncryptedKey clause was not wrapped with the required encryption token 'System.IdentityModel.Tokens.X509SecurityToken'.
表示证书不匹配。我正在加载一个名为 MaternumCertificateClient 的客户端,我需要 MaternumCertificateServer。 此外,行
pdfService.ClientCredentials.ClientCertificate.Certificate = cert;
pdfService.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerTrust;
pdfService.ClientCredentials.ServiceCertificate.DefaultCertificate = GetCertificate("MaternumCertificateClient");
实际上不需要;证书是从端点的身份中读取的,我的设置工作不需要这些设置。
正如您所指出的,除了我们使用证书对客户端进行身份验证外,无需在客户端提供证书。比如下面的配置。
<wsHttpBinding>
<binding name="WSHttpBinding_ICalculator">
<security mode="Message">
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
请参考以下内容link。客户端提供证书来代表身份并加密通信。
https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-a-certificate-client
在服务器端的绑定配置中,只需要 username/password 凭据。
<security>
<message clientCredentialType="UserName" negotiateServiceCredential="false"
establishSecurityContext="false" />
</security>
证书通过使用 public 密钥加密 soap 消息来提供消息安全性。通信过程中,客户端和服务器端交换证书的public密钥。我们甚至可以指定服务器域名作为标识,它只是用来确保服务器的身份。
此外,如果您的回复解决了您的问题。请标记它有用,以便帮助遇到类似问题的其他人。
如果有什么我可以帮忙的,请随时告诉我。