How to supply both UserName and Client Certificate

2019-08-06 04:49发布

Consider a WCF service in which the intent is to have Client Certificates required at the Transport layer (Client Certificates set to "Required" in IIS). As well, there will be username authentication at the message layer.

Now I've seen this question already:

WCF Client Certificate AND UserName Credentials forbidden

and I can somewhat understand what's going on there and realize that inherently WCF does not allow both. I went through the same steps in code as the poster in the link referenced above and found the same result...the message-level UserName credentials were being passed (in the SOAP header in my case), but the Client Cert (despite being attached when the request client is viewed in VS debug) was not actually being processed by the endpoint.

So now comes the part that has me confused. I decided to hack it somewhat. I'm wondering why this works exactly like I'm wanting...it gets past IIS Client Cert requirement, the UserName gets passed to the WCF Service and all just works. Yet WCF does not allow me to do it just using WCF config files or code (that I can find). Why?

            // sets up a proxy client based on endpoint config
            // basically just here to get the URL.
            this.InitializeSubmitClient();

            // these get used to create the HttpWebRequest
            string url = this.submitClient.Endpoint.Address.ToString();
            string action = "SubmitCDA";

            // this deserializes an XML file which is the "shell" of SOAP document and inject username/password into SOAP Security node
            XmlDocument soapEnvelopeXml = XMLHelper.CreateSoapDocument(this.txtSubmitCdaXmlFile.Text, this.txtAdAccount.Text, this.txtPassword.Text);
            HttpWebRequest webRequest = XMLHelper.CreateWebRequest(url, action);

            // saves the SOAP XML into the webRequest stream.
            XMLHelper.InsertSoapEnvelopeIntoWebRequest(soapEnvelopeXml, webRequest);

            // attach the cert
            if (this.chkSendClientCert.Checked)
            {
                X509Certificate myCert = X509Certificate.CreateFromCertFile(@"C:\temp\CDX-IHAT_DevClientCert.cer");
                webRequest.ClientCertificates.Add(myCert);
            }
            else
            {
                webRequest.ClientCertificates.Clear();
            }

            // begin async call to web request.
            IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);

To further complicate matters, the WCF Service that this applies to is a BizTalk service.

标签: c# wcf soap ssl
1条回答
够拽才男人
2楼-- · 2019-08-06 05:15

Here's how I ended up doing it.

The Server Config:

  <customBinding>
    <binding name="CustomCDARequestEndpointBinding">                    
      <textMessageEncoding messageVersion="Soap11" />
      <security authenticationMode="UserNameOverTransport" />
      <httpsTransport requireClientCertificate="true" />
    </binding>
  </customBinding>

The Client Config:

<system.ServiceModel>
  <bindings>
    <customBindings>
      <binding name="CustomBinding_ITwoWayAsync">
          <security defaultAlgorithmSuite="Default"
            authenticationMode="UserNameOverTransport"
            requireDerivedKeys="true"
            includeTimestamp="true"
            messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
          >
            <localClientSettings detectReplays="false" />
            <localServiceSettings detectReplays="false" />
          </security>
          <textMessageEncoding messageVersion="Soap11" />
          <httpsTransport requireClientCertificate="true" />
      </binding>
    </customBinding>
  </bindings>
  <behaviors>
    <endpointBehaviors>
      <behavior name="ohBehave">
        <clientCredentials useIdentityConfiguration="false">
        <clientCertificate findValue="6D0DBF387484B25A16D0E3E53DBB178A366DA954" storeLocation="CurrentUser"
          x509FindType="FindByThumbprint" />            
        </clientCredentials>          
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <client>
     <endpoint address="https://myservice/CDASubmitService/CDASubmit.svc"
        binding="customBinding" bindingConfiguration="SubmitDev" behaviorConfiguration="ohBehave"
        contract="CDASubmitService.CDASubmit" name="SubmitDev" />
  </client>
</system.serviceModel>

The key to getting it working was the <httpsTransport requireClientCertificate="true" /> element and the <security authenticationMode="UserNameOverTransport" element/attribute.

This configuration allowed me to submit a message to a WCF (BizTalk) service completely through configuration files, with no changes to actual code. It still allows me to submit to it VIA WebRequest as well, as shown above.

I have to give credit to this post:

WCF Client Certificate AND UserName Credentials forbidden

as well as this one:

Translate non-BizTalk WCF config into BizTalk WCF-Custom endpoint

for finally getting me on the right track. I always shied away from Custom Bindings in WCF because I assumed it was overkill, but they are really nothing crazy, just a way to supply more detailed config than is available out of the box.

查看更多
登录 后发表回答