使用代码隐藏而不是 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

就是这样。我想我在错误的地方设置了客户端证书,如果不是通过 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密钥。我们甚至可以指定服务器域名作为标识,它只是用来确保服务器的身份。
此外,如果您的回复解决了您的问题。请标记它有用,以便帮助遇到类似问题的其他人。
如果有什么我可以帮忙的,请随时告诉我。