在发送请求之前需要帮助在我的 WCF 客户端中操作 SOAP 标头 [英] Need help with manipulating SOAP header in my WCF client before sending request

查看:20
本文介绍了在发送请求之前需要帮助在我的 WCF 客户端中操作 SOAP 标头的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a unique requirement where I need to send a highly customized soap header in a request to a external vendor. The only way my WCF client can interact with their web service is using a combination of Username token and message signing the entire envelope(see vendor provided soap header below).

   <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>

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

I have tried combination of wshttpbinding, custom binding with different behaviors in "app.config". I was unable to replicate the SOAP header shown above nor was I able connect to web service. It is just the way the header needs to be setup that I cannot configure thru app.config. So I asked my vendor on how I can replicate the header within the WCF client. They sent me a block of code(x509 Authentication.cs) that they tested and confirmed that it works(no idea how it worked for them). Basically I "somehow" need to intercept the request as my WCF client sends the request to the vendor , after intercepting it somehow pass the SOAP body as an input to a method ( CreateX509SoapEnvelope("SOAP body")).I have Attached complete code x509 Authentication.cs below

    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>
";

        // 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");
        //}
    }

}

}

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

The code accepts soap body & stitches together the custom header and signs the entire envelope returning header as a string. I need to take this string and pass it back to the request and send it on its way to the vendor. It sounds way too complicated for me theoritically. But with research I found that there was a way to trap the outgoing message by implementing the IClientMessageInspector interface and overriding the "BeforeSendRequest" method. I got the part of the code working where when WCF client is executed the "BeforeSendRequest" method is being called. But now I am stuck at how to extract the SOAP body from the outgoing message (I see the body when I debug) and send it to as an input to Createx509Envelope method and then take the output of the method and put it back in the "request" object and send the message to the vendor .. see my BeforeSendRequest method implementation(nothing much in there I am stuck)

    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 标头的第一次,这对我来说很复杂.赶时间.请帮忙!!!!!!

If there is better way of implementing it ? please provide samples.. this is the first where I had to send a custom SOAP header to vendor and its complicated for me. Running short on time. PLEASE HELP!!!!!!

推荐答案

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

Check out the following article which shows you an example of how to implement an IClientMessageInspector which alters the message and injects a custom header.

通过 WCF 行为处理自定义 SOAP 标头

首先,您需要定义一个自定义标头来表示 SOAP 标头的内容.为此,请创建您自己的 MessageHeader 班级.

First you need to define a custom header to represent the contents of a SOAP header. To do so create your own descendant of the MessageHeader class.

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

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

Create an IClientMessageInspector implementation that injects your custom header just before sending the request (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 管道,但您已经了解了这一部分.

Now you need to add your custom message inspector to the WCF pipeline, but you already got this part covered.

BeforeSendRequest(ref Message request, IClientChannel channel) 的 Message 参数可用于使用 消息类型 (ToString(), GetBody(), GetReaderAtBodyContents()...等.

The Message parameter of the BeforeSendRequest(ref Message request, IClientChannel channel) can be used to read the SOAP message using one of the methods of the Message type (ToString(), GetBody(), GetReaderAtBodyContents()...etc.).

要获取消息正文,请使用 GetReaderAtBodyContents() 方法,该方法返回 XmlDictionaryReader 对象.使用此 XML 阅读器以字符串形式检索正文.

To get the body of the message use the GetReaderAtBodyContents() method which returns an XmlDictionaryReader object. Use this XML reader to retrieve the body as a string.

例如:

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

这篇关于在发送请求之前需要帮助在我的 WCF 客户端中操作 SOAP 标头的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆