这个问题让我抓狂。我必须使用由dotNet端点应用程序验证的Java签署SOAPMessage。供应商给了我们一个示例dotNet客户端应用程序作为示例,所以我受到了这个示例应用程序的启发,以及互联网上的几个链接,如:
http://www.java2s.com/Tutorial/Java/0410__Web-Services-SOA/SignSOAPmessage.htm
Java equivalent of C# XML signing method
正文的结构(签名内容)是:
<Body>
<inbound>
<content>...text content...</content>
</inbound>
</Body>
到目前为止,只有当文本内容不包含任何换行符时,我才能够生成相同的签名(使用我的代码和供应商提供的示例)。只要我的文本包含行分隔符,签名值就不再相同了。
经过几天的搜索,我认为这个问题与规范化有关。
在Java中,以下代码:
...
Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
Element body = getNextSiblingElement(header);
byte outputBytes[] = c14n.canonicalizeSubtree(body);
FileUtils.writeByteArrayToFile(new File("C:\\tmp\\Canonicalized_java.xml"),outputBytes);
...
产地:
<s:Body xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" Id="uuid"><inbound><content>ABC
DEF
HIJ
</content></inbound></s:Body>
在dotNet(c#)中,代码如下:
...
using (MemoryStream nodeStream = new MemoryStream())
{
XmlWriterSettings ws = new XmlWriterSettings();
ws.NewLineHandling = NewLineHandling.None;
using (XmlWriter xw = XmlWriter.Create(nodeStream, ws))
{
soapRequest.GetElementsByTagName("Body")[0].WriteTo(xw);
xw.Flush();
}
nodeStream.Position = 0;
XmlDsigExcC14NTransform transform = new XmlDsigExcC14NTransform();
transform.LoadInput(nodeStream);
using (MemoryStream outputStream = (MemoryStream)transform.GetOutput(typeof(Stream)))
{
File.WriteAllBytes("C:\\tmp\\Canonicalized_dotNet.xml", outputStream.ToArray());
}
}
...
产地:
<s:Body xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" Id="uuid"><inbound><content>ABC
DEF
HIJ
</content></inbound></s:Body>
换行符只是LF。 (请注意,原始内容仅包含CR作为换行符。)
如果需要,我可以提供更多源代码,但也许有人确切知道这里发生了什么。任何帮助将非常感激。请注意,我无法更改dotNet端点应用程序验证XML签名值的方式。
修改 以下是消息正文的构建方式。我不明白为什么这不会产生与Aaryn的anwser相同的结果:
public static void main(String[] args) throws Exception {
com.sun.org.apache.xml.internal.security.Init.init();
SOAPMessage soapMessage = MessageFactory.newInstance().createMessage();
SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
SOAPBody soapBody = soapEnvelope.getBody();
SOAPElement inboundMessage = soapBody.addChildElement("inbound");
SOAPElement payload = inboundMessage.addChildElement("content");
payload.addTextNode("ABC\rDEF\rGHI\r");
Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
byte outputBytes[] = c14n.canonicalizeSubtree(soapBody);
System.out.println(new String(outputBytes));
}
输出:
<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><inbound><content>ABC
DEF
GHI
</content></inbound></SOAP-ENV:Body>
编辑2 我做了一些测试,尝试在运行时构建XML而不是解析输入String。这是我的最后一次试验:
public static void main(String[] args) throws Exception {
com.sun.org.apache.xml.internal.security.Init.init();
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element body = doc.createElement("Body");
Element inbound = doc.createElement("inbound");
Element content = doc.createElement("content");
content.appendChild(doc.createTextNode("ABC\rDEF\rGHI\r"));
doc.appendChild(body);
body.appendChild(inbound);
inbound.appendChild(content);
Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
byte outputBytes[] = c14n.canonicalizeSubtree(body);
System.out.println(new String(outputBytes));
}
输出仍然是
<Body><inbound><content>ABC
DEF
GHI
</content></inbound></Body>
我真的不明白。为什么CR被
而不是LF替换,就像从解析输入字符串构建文档时那样?
答案 0 :(得分:2)
如果您发布的文档是您的实际输入,那么您的两个规范化都可能存在问题。在这种情况下,它们不会保留开始和结束标记之间的前导空格。在规范化之前,您是否正在对文档进行任何处理?在一个简单的测试程序中:
public static String test = "<Body>\n"
+ " <inbound>\n"
+ " <content>...text\r content\n...</content>\n"
+ " </inbound>\n"
+ "</Body>";
public static void main(String[] args) throws Exception {
com.sun.org.apache.xml.internal.security.Init.init();
Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
Element body = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(test.getBytes())).getDocumentElement();
byte outputBytes[] = c14n.canonicalizeSubtree(body);
System.out.println(new String(outputBytes));
}
空白的输出应保留原样(受行分隔符规范化)。我相信你在Java中规范化之前的代码中做了一些事情,它正在逃避你的回车。对于大多数人来说,XML规范化的空白保留是令人惊讶的,请参阅W3C Canonicalization Specs。在我第一次处理它时,它肯定会让我失望。
您使用的是XML Digital signatures还是自定义签名?如果是W3C,您应该能够使用本机数字签名库。
回复编辑1&amp; 2
Document.createTextNode(String data)方法为您转义数据。这是为了防止您创建无效的xml文档(它也将转义&gt;,&lt;和其他字符)。它不是执行XML规范化定义的行分隔符规范化,而是转义字符,以便可以恢复原始文本。由于您似乎想要自己执行行分隔符规范化,因此您应该手动对输入字符串执行此清理:
String text = "ABC\rDEF\rGHI\r";
payload.addTextNode(text.replace("\r", "\n"));