Нужна помощь с манипулированием заголовком SOAP в моем клиенте WCF перед отправкой запроса - PullRequest
3 голосов
/ 20 августа 2011

У меня есть уникальное требование, когда мне нужно отправить сильно настроенный мыльный заголовок в запросе внешнему поставщику.Единственный способ, которым мой клиент WCF может взаимодействовать со своей веб-службой, - это использовать комбинацию токена имени пользователя и подписи сообщения во всем конверте (см. Ниже заголовок мыла, предоставленный поставщиком).

   <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, показанный выше, и я не смог подключиться к веб-сервису.Это просто способ установки заголовка, который я не могу настроить через app.config.Поэтому я спросил своего поставщика о том, как я могу скопировать заголовок в клиенте WCF.Они прислали мне блок кода (x509 Authentication.cs), который они протестировали, и подтвердили, что он работает (понятия не имею, как это работает для них).По сути, мне «как-то» нужно перехватить запрос, так как мой клиент WCF отправляет запрос поставщику, после перехвата он каким-то образом передает тело SOAP в качестве входных данных для метода (CreateX509SoapEnvelope («тело SOAP»)). Я прикрепил полный код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");
        //}
    }

}

}

Код принимает мыльное тело и соединяет пользовательский заголовок и подписывает весь возвращаемый заголовок конверта в виде строки.Мне нужно взять эту строку, передать ее обратно в запрос и отправить по пути к поставщику.Это звучит слишком сложно для меня теоретически.Но в ходе исследований я обнаружил, что существует способ перехватить исходящее сообщение, реализовав интерфейс 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 Ответ

2 голосов
/ 20 августа 2011

Ознакомьтесь со следующей статьей, в которой показан пример реализации IClientMessageInspector, который изменяет сообщение и внедряет пользовательский заголовок.

Обработка пользовательских заголовков SOAP с помощью поведения WCF

Сначала необходимо определить пользовательский заголовок для представления содержимого заголовка 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, но вы уже рассмотрели эту часть.

Параметр Message запроса BeforeSendRequest (ref Message request, IClientChannelканал) может использоваться для чтения сообщения SOAP с использованием одного из методов Тип сообщения (ToString (), GetBody (), GetReaderAtBodyContents () ... и т. д.).

Чтобы получить тело сообщения, используйте метод GetReaderAtBodyContents (), который возвращает объект XmlDictionaryReader .Используйте этот XML-ридер для извлечения тела в виде строки.

Например:

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