[英]Need help with manipulating SOAP header in my WCF client before sending request
我有一個獨特的要求,我需要在請求中向外部供應商發送高度自定義的soap標頭。 我的WCF客戶端可以與其Web服務進行交互的唯一方法是使用用戶名令牌和郵件簽名整個信封的組合(請參閱下面的供應商提供的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>
我嘗試了在“app.config”中使用wshttpbinding,自定義綁定和不同行為的組合。 我無法復制上面顯示的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");
//}
}
}
}
該代碼接受soap body並將自定義標頭拼接在一起,並將整個信封作為字符串返回標頭。 我需要獲取此字符串並將其傳遞回請求並將其發送到供應商。 這聽起來對我來說太復雜了。 但通過研究,我發現有一種方法可以通過實現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標頭,這對我來說很復雜。 時間緊迫。 請幫忙!!!!!!
查看以下文章,其中向您展示了如何實現IClientMessageInspector的示例,該IClientMessageInspector可以更改消息並注入自定義標頭。
首先,您需要定義一個自定義標頭來表示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類型的方法之一(ToString(),GetBody(), GetReaderAtBodyContents()等)讀取SOAP消息。
要獲取消息正文,請使用返回XmlDictionaryReader對象的GetReaderAtBodyContents()方法。 使用此XML閱讀器將主體檢索為字符串。
例如:
using (XmlDictionaryReader reader = message.GetReaderAtBodyContents())
{
string content = reader.ReadOuterXml();
//...
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.