在发送请求之前,需要帮助在我的WCF客户端中操作SOAP标头

时间:2011-08-20 03:07:53

标签: wcf c#-4.0 c#-3.0 wcf-security wcf-client

我有一个独特的要求,我需要在请求中向外部供应商发送高度自定义的soap标头。我的WCF客户端可以与其Web服务进行交互的唯一方法是使用Username令牌和邮件签名整个信封的组合(请参阅下面的供应商提供的soap标题)。

   <soapenv:Envelope xmlns:bsvc="urn:com.workday/bsvc"   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Header>
   <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
  <wsse:UsernameToken wsu:Id="UsernameToken-20" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <wsse:Username>Cert509User</wsse:Username>
  </wsse:UsernameToken>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
        <DigestValue>Lx8YS/gC/oTagK0cn2rzGCQcYSSiZC9CKqIFqd/X8zw=</DigestValue>
      </Reference>
    </SignedInfo>
      <SignatureValue>p9Z1inN//gcDH85KFfd3RB6jY9hEy93ZqSj1l+sGakpvTgyivTbD0mDXKMpEwQVxCqtsEP9r78voxjlAbgM5PJyMQsmIxz+KQ45LyaA8dDdA4X4TIJ89dgvacT5PY0rtxJD2u2T5cRvQJ7p9etJL4FcQMI9I6XyU7DcKFOuRehE=</SignatureValue>
    <KeyInfo>
      <X509Data>
        <X509Certificate>MIIDuzCCAqOgAwIBAgIQK2RKs3P21+p4XAV83a/QLjANBgkqhkiG9w0BAQUFADCBrjETMBEGCgmSJomT8ixkARkWA2NvbTEaMBgGCgmSJomT8ixkARkWCm1hc3RlcmNhcmQxHTAbBgNVBAoTFE1hc3RlckNhcmQgV29ybGRXaWRlMSQwIgYDVQQLExtHbG9iYWwgSW5mb3JtYXRpb24gU2VjdXJpdHkxNjA0BgNVBAMTLUlURiBNQyBQcm9kdWN0aW9uIE5ldHdvcmsgQXBwbGljYXRpb25zIHN1YiBDQTAeFw0xMTA4MDQwOTQwNDlaFw0xNTA4MDMwOTA2NDRaMIGoMQswCQYDVQQGEwJVUzERMA8GA1UECBMITWlzc291cmkxFDASBgNVBAcTC1NhaW50IExvdWlzMTQwMgYDVQQKEytNYXN0ZXJDYXJkIFdvcmxkV2lkZSAtIENvbW1vbiBQcm9kSW5mcmEgU1NMMREwDwYDVQQLEwhzaWduaW5nMTEnMCUGA1UEAxMec3RhZ2Uud29ya2RheUhSLm1hc3RlcmNhcmQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCt4MlJCVNcmXiQIg8pxR4JsR0QpIuBCPadIAo849CRLpZglIKRWrTlxRIBC2YQeW3OkuDEdqYU6wJzn9m6GHTbmOSAy21aVR0eOqQLHltXytdzOJG92HW1IlBVuzwmMKwzEUjhVatLRQjKvTs6TjJ7egfzO8H2yolU59fq/zLcpQIDAQABo10wWzAfBgNVHSMEGDAWgBQCt+lVDTcnQt+zKa7QBi4/hEiVUzAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQUM23TyPCInFlw2PnukzGOn8kKldcwDQYJKoZIhvcNAQEFBQADggEBAJeAcKk3YWN12frCQSuKzO4qTBNo+QjUjXEHfYuUl8i2pJHs6tDuDkX36RYPWyXLyMPXHSOoomlVmsCprGLqfTGBf1jW/e7Re3sg3/k1iJFg3f1mMKxGP0MuUvuofc/Nj+ezvvl/Nswn3bsAMgvktM+OR5KEhi293qlix87mpvmuvDUw1ZfoQpgN8AvdiQiRWBN2SXahwzGJo+gRjy6EUGdNgc+lsPDkkKxF6csWsb59yip4t7nTbSjqi5XCjZYfMAG5cDhDELtqge5i1W+1a0mP12xKb5P205HSjH9jF/N67CwOBxuuUXaexsqbLaRfL0Dxo0oFwusnIQ1A2qMgg1c=</X509Certificate>
      </X509Data>
    </KeyInfo>
  </Signature>
</wsse:Security>
</soapenv:Header>
<soapenv:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
SOAP BODY goes HERE
</soapenv:Body>
</soapenv:Envelope>

我尝试过将wshttpbinding,自定义绑定与“app.config”中的不同行为结合起来。我无法复制上面显示的SOAP标头,也无法连接到Web服务。这只是标题需要设置的方式,我无法通过app.config配置。所以我问我的供应商如何在WCF客户端中复制标头。他们给我发了一个代码块(x509 Authentication.cs),他们测试并确认它有效(不知道它是如何工作的)。基本上我“以某种方式”需要拦截请求,因为我的WCF客户端将请求发送给供应商,在拦截后以某种方式将SOAP主体作为输入传递给方法(CreateX509SoapEnvelope(“SOAP body”))。我已经附加了完整的代码x509 Authentication.cs

    class x509_Authentication
     {
      public string CreateX509SoapEnvelope(string xml)
      {
        string soapXML;
        soapXML = "<soapenv:Envelope xmlns:bsvc=\"urn:com.workday/bsvc\" 
                   xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">";
        soapXML += "<soapenv:Header>\n";

        // Add security block for X.509 certificate
        soapXML = "<wsse:Security xmlns:wsse=\"http://docs.oasis-
                   open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">";
        soapXML += "<wsse:UsernameToken wsu:Id=\"UsernameToken-20\"     
                    xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-
                     wss-wssecurity-utility-1.0.xsd\">";
        soapXML += "<wsse:Username>Cert509User</wsse:Username>";
        soapXML += "</wsse:UsernameToken>";
        soapXML += "</wsse:Security>";

        soapXML += "</soapenv:Header>" + xml + "</soapenv:Envelope>";

        // Sign Envelope
        soapXML = CreateSignatureBlock(soapXML, "wsse:Security");

        // Verify that the XML was signed properly
        VerifySignedXml(soapXML);

        return soapXML;
    }

    public string CreateSignatureBlock(string xml, string sParentSignatureTagName)
    {
        try
        {
            string certificatePath="C:\\Users\\user3434\\Desktop\\certfolder\\cert.p12";
            //load xml into a dom
            XmlDocument xd = new XmlDocument();
            xd.LoadXml(xml);

            // Set Certificate
            System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new X509Certificate2(certificatePath, "changeit");
            //System.Security.Cryptography.X509Certificates.X509Certificate2 cert = x509_Authentication.GetCertificateFromStore();
            SignedXml signedXml = new SignedXml(xd);
            signedXml.SigningKey = cert.PrivateKey;

            // Create a new KeyInfo object.
            KeyInfo keyInfo = new KeyInfo();
            keyInfo.Id = "";

            // Load the certificate into a KeyInfoX509Data object
            // and add it to the KeyInfo object.
            KeyInfoX509Data keyInfoData = new KeyInfoX509Data();
            keyInfoData.AddCertificate(cert);
            keyInfo.AddClause(keyInfoData);

            // Add the KeyInfo object to the SignedXml object.
            signedXml.KeyInfo = keyInfo;

            // Need to use External Canonicalization method.
            signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";

            // Create a reference to be signed.
            Reference reference = new Reference();
            reference.Uri = "";

            // Add an enveloped transformation to the reference.
            XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
            reference.AddTransform(env);
            reference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";

            // Add the reference to the SignedXml object.
            signedXml.AddReference(reference);

            // Add the Signature Id
            signedXml.Signature.Id = "";

            // Compute the signature.
            signedXml.ComputeSignature();

            // Get the XML representation of the signature and save
            // it to an XmlElement object.
            XmlElement xmlDigitalSignature = signedXml.GetXml();

            // Append the Signature element to the XML document. It will find the element after which we want to insert the signature
            XmlNodeList nodeList = xd.GetElementsByTagName(sParentSignatureTagName);
            if (nodeList.Count > 0)
            {
                XmlNode headerNode = nodeList[0];
                headerNode.AppendChild(xd.ImportNode(xmlDigitalSignature, true));
            }

            return xd.InnerXml;
        }
        catch
        {
            return xml;
        }
    }

    public void VerifySignedXml(String xml)
    {

        // Create a new XML document.
        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.PreserveWhitespace = true;
        xmlDocument.LoadXml(xml);

        // Create a new SignedXml object and pass it
        // the XML document class.
        SignedXml signedXml = new SignedXml(xmlDocument);

        // Add an enveloped transformation to the reference.
        XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();

        Reference reference = new Reference();
        reference.AddTransform(env);
        signedXml.AddReference(reference);

        // Find the "Signature" node and create a new XmlNodeList object.
        XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");

        // Load the signature node.
        signedXml.LoadXml((XmlElement)nodeList[0]);

        // Check the signature and return the result.
        //if (!signedXml.CheckSignature(cert,true))
        //if (!signedXml.CheckSignature(new X509Certificate2(certificatePath, "sdfdf"), true))
        //{
        //    log.Error("Invalid Signature");
        //}
    }

}

}

该代码接受肥皂身体&amp;将自定义标题拼接在一起并将整个信封标记为字符串。我需要获取此字符串并将其传递回请求并将其发送到供应商。这听起来对我来说太复杂了。但通过研究,我发现有一种方法可以通过实现IClientMessageInspector接口并覆盖“BeforeSendRequest”方法来捕获传出消息。我得到了代码的一部分,当WCF客户端被执行时,正在调用“BeforeSendRequest”方法。但是现在我被困在如何从传出消息中提取SOAP主体(我在调试时看到正文)并将其作为Createx509Envelope方法的输入发送,然后获取方法的输出并将其放回“请求“对象并将消息发送给供应商..请参阅我的BeforeSendRequest方法实现(没有什么在那里我被卡住了)

    public string RequestMessage { get; set; }
        public string ResponseMessage { get; set; }


  object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
        {

            **HOW TO EXTRACT SOAPBODY FROM “.ServiceModel.Channels.Message request” OBJECT and CONVERT THAT TO STRING ??????????**

x509_Authentication x509 = new x509_Authentication();
            this.ResponseMessage = x509.CreateX509SoapEnvelope(SOAP Body);

**TAKE THE RESPONSE MESSAGE AND CONVERT BACK TO “.ServiceModel.Channels.Message request” AND SEND THE REQUEST ALONG???????????**

            return null;
        }

如果有更好的实施方式吗?请提供示例..这是我第一次向供应商发送自定义SOAP标头,这对我来说很复杂。时间紧迫。请帮忙!!!!!!

1 个答案:

答案 0 :(得分:2)

查看以下文章,其中向您展示了如何实现IClientMessageInspector的示例,该IClientMessageInspector会更改消息并注入自定义标头。

Handling custom SOAP headers via WCF Behaviors

首先,您需要定义一个自定义标头来表示SOAP标头的内容。为此,请创建自己的MessageHeader类后代。

public class MyHeader : MessageHeader
{ 
    //... 
}

创建一个IClientMessageInspector实现,在发送请求之前注入您的自定义标头(BeforeSendRequest)。

public class CustomMessageInspector : IClientMessageInspector
{
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
        request = buffer.CreateMessage();
        request.Headers.Add(new MyHeader());
        return null;
    }

    //...
}

现在您需要将自定义消息检查器添加到WCF管道,但您已经覆盖了此部分。

BeforeSendRequest的Message参数(ref消息请求,IClientChannel通道)可用于使用Message type(ToString(),GetBody(),{{3}的方法之一读取SOAP消息} ...等)。

要获取消息正文,请使用返回GetReaderAtBodyContents()对象的GetReaderAtBodyContents()方法。使用此XML阅读器将主体检索为字符串。

例如:

using (XmlDictionaryReader reader = message.GetReaderAtBodyContents())
{
    string content = reader.ReadOuterXml();
    //...   
}