Canonicalize XML in Java

  sonic0002        2016-01-20 01:39:45       18,990        0          English  简体中文  繁体中文  ภาษาไทย  Tiếng Việt 

当需要创建数字签名并发送给对等方进行验证时,通常会使用 XML 规范化。由于数字签名是基于 XML 数据创建的,因此 XML 数据必须先进行规范化,然后才能计算其签名值。即使是一个额外的空格也可能会影响计算出的签名值,因此它必须遵循一些规则来规范化 XML 数据,使其具有标准格式。这就是 W3C 创建 规范 XML 版本 1.1 规范的原因。

此规范提供了格式化元素节点、属性节点和命名空间节点等的规则。不同的编程语言已经实现了此规范,因此我们可以使用它们轻松地规范化 XML 数据,而无需了解有关该规范的详细信息。

在本教程中,我们不会深入探讨该规范的细节,我们将只关注如何调用 Java API 来规范化 XML 数据。基于 XPath 数据模型,XML 文档由一组节点(节点集)表示。而 Java 中的 XML API 接收一个要规范化的 Data 对象。为了演示规范化,我们将创建一个自定义的 NodeSetDataImpl,它实现了 NodeSetData 和 Iterator 接口。

import java.util.Iterator;
import javax.xml.crypto.NodeSetData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;

public class NodeSetDataImpl implements NodeSetData, Iterator {
	private Node ivNode;
	private NodeFilter ivNodeFilter;
	private Document ivDocument;
	private DocumentTraversal ivDocumentTraversal;
	private NodeIterator ivNodeIterator;
	private Node ivNextNode;

	public NodeSetDataImpl(Node pNode, NodeFilter pNodeFilter) throws Exception {
		ivNode = pNode;
		ivNodeFilter = pNodeFilter;

		if (ivNode instanceof Document) {
			ivDocument = (Document) ivNode;
		} else {
			ivDocument = ivNode.getOwnerDocument();
		}

		ivDocumentTraversal = (DocumentTraversal) ivDocument;
	}

	private NodeSetDataImpl(NodeIterator pNodeIterator) {
		ivNodeIterator = pNodeIterator;
	}

	public Iterator iterator() {
		NodeIterator nodeIterator = ivDocumentTraversal.createNodeIterator(ivNode, NodeFilter.SHOW_ALL, ivNodeFilter, false);
		return new NodeSetDataImpl(nodeIterator);
	}

	private Node checkNextNode() {
		if (ivNextNode == null && ivNodeIterator != null) {
			ivNextNode = ivNodeIterator.nextNode();
			if (ivNextNode == null) {
				ivNodeIterator.detach();
				ivNodeIterator = null;
			}
		}
		return ivNextNode;
	}

	private Node consumeNextNode() {
		Node nextNode = checkNextNode();
		ivNextNode = null;
		return nextNode;
	}

	public boolean hasNext() {
		return checkNextNode() != null;
	}

	public Node next() {
		return consumeNextNode();
	}

	public void remove() {
		throw new UnsupportedOperationException("Removing nodes is not supported.");
	}

	public static NodeFilter getRootNodeFilter() {
		return new NodeFilter() {
			public short acceptNode(Node pNode) {
				if (pNode instanceof Element && pNode.getParentNode() instanceof Document) {
					return NodeFilter.FILTER_SKIP;
				}
				return NodeFilter.FILTER_ACCEPT;
			}
		};
	}
}

然后我们需要创建一个 CanonicalizationMethod 来执行实际的规范化。这里有几个选择:

CanonicalizationMethod.INCLUSIVE
CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS
CanonicalizationMethod.EXCLUSIVE
CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS

在本教程中,我们将只演示包含式规范化。以下是执行此操作的代码。

import java.io.ByteArrayInputStream;
import javax.xml.crypto.Data;
import javax.xml.crypto.OctetStreamData;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;

public class CanonicalizationTest {
	private static String INPUT = 
			"" +
			"hello" +
			"world" +
			"";
	
	public static void main(String[] args){
		transform(INPUT);
	}
	
	/**
	 * Create new document with data
	 * 
	 * @param xml
	 * @return
	 */
	private static Document createNewDocument(String xml){
		try{
			byte[] bytes = xml.getBytes("UTF-8");
			ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
			
			DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
			fac.setNamespaceAware(true);
			
			DocumentBuilder docBuilder = fac.newDocumentBuilder();
			
			Document doc = docBuilder.parse(bin);

			return doc;
		} catch (Exception ex){
			ex.printStackTrace();
		}
		return null;
	}
	
	/**
	 * Transform a XML string
	 * 
	 * @param xml
	 */
	private static void transform(String xml){
		Document doc = createNewDocument(xml);
		
		try{
			Data data = new NodeSetDataImpl(doc, NodeSetDataImpl.getRootNodeFilter());
			XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
			CanonicalizationMethod canonicalizationMethod = fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec)null);
			// Doing the actual canonicalization
			OctetStreamData transformedData =(OctetStreamData) canonicalizationMethod.transform(data, null);
			byte[] bytes = tool.Util.readStream(transformedData.getOctetStream());
			String str = new String(bytes);
			System.out.println(str);
		} catch (Exception ex){
			ex.printStackTrace();
		}
	}
}

首先让我们看一下输出:

<my:Node xmlns:my="http://example.com" Id="1">hello</my:Node><my:Node xmlns:my="http://example.com" Id="2">world</my:Node>

测试将首先创建一个 Document,然后将此 Document 传递给 NodeSetDataImpl 以创建要规范化的节点集。从输出中我们可以看到,Root 节点在规范化数据中被删除,这是因为 NodeSetDataImpl 中的 NodeFilter 过滤了此节点。接下来,第二个 my:Node 在规范化形式中,xmlns:my 节点在 Id 节点之前。这是基于规范 XML 规范,其中节点应按词汇顺序排列。

对于其他规范化方法,这里的逻辑是相同的。不同之处在于输出不相同,因为不同的规范化方法有不同的规则。

JAVA  JAVA SECURITY  XML 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Can you write code like this?

A TV program visits one game company. The programmer doesn't want to showcase his programming skills to the world. So he did this. Can you write code like this?