我有一个独特的要求,我需要在请求中向外部供应商发送高度自定义的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标头,这对我来说很复杂。时间紧迫。请帮忙!!!!!!
答案 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();
//...
}