如何通过XSLT手动缩进XML且不破坏XMLDSIG签名?
我之前也碰到过一模一样的XMLDSIG签名缩进问题——既要让XML其他部分整洁可读,又不能碰签名的任何字符(包括标签前后的空格换行),结合XSLT3、JAXB和XSD绑定的配置,完全能解决这个问题,具体方案如下:
1. 调整XSLT3样式表,双管齐下保护签名
suppress-indentation只能解决签名内部的缩进问题,但签名标签本身的前置空白还需要额外处理。我们可以通过模板匹配,让<Signature>和它的前一个元素紧挨着输出,同时移除所有可能的空白节点:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <!-- 开启全局缩进,同时禁止Signature内部被缩进 --> <xsl:output method="xml" indent="yes" suppress-indentation="ds:Signature" encoding="UTF-8"/> <!-- 匹配所有元素:如果它的下一个兄弟是Signature,输出后不换行 --> <xsl:template match="*[following-sibling::*[1][local-name()='Signature' and namespace-uri()='http://www.w3.org/2000/09/xmldsig#']]"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> <!-- 这里不输出任何换行/空格,直接衔接下一个Signature元素 --> </xsl:template> <!-- 移除Signature前面的空白文本节点 --> <xsl:template match="text()[preceding-sibling::*[1][local-name()='Signature' and namespace-uri()='http://www.w3.org/2000/09/xmldsig#']]"/> <!-- 默认模板:处理其他所有节点,正常输出缩进 --> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
这个样式表做了两件核心事:
- 用
suppress-indentation锁定Signature内部结构,防止缩进破坏签名内容 - 通过模板匹配,强制Signature和前一个元素处于同一行,同时清除Signature前的所有多余空白
2. XSD配合JAXB绑定,从源头避免空白插入
XSD本身不控制XML格式,但可以通过JAXB绑定文件(binding.xjb),让JAXB在生成代码时对Signature元素保持严格的紧凑处理,避免序列化时自动插入空白:
<jaxb:bindings version="2.1" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <jaxb:bindings schemaLocation="your-schema-file.xsd" node="/xsd:schema"> <!-- 针对Signature元素设置紧凑序列化规则 --> <jaxb:bindings node="//xsd:element[@name='Signature']"> <jaxb:property> <jaxb:baseType name="javax.xml.bind.JAXBElement<ds:SignatureType>"/> </jaxb:property> </jaxb:bindings> </jaxb:bindings> </jaxb:bindings>
这个配置会让JAXB生成的Signature相关代码,在序列化时不会自动添加任何多余的空白节点,和XSLT的处理形成双重保障。
3. JAXB Marshaller代码配置,完全交给XSLT控制格式
一定要禁用JAXB自带的格式化输出,让XSLT全权负责缩进处理,否则JAXB的默认格式化会打乱我们的设置:
// 初始化JAXB上下文 JAXBContext jaxbContext = JAXBContext.newInstance(YourRootModelClass.class); Marshaller marshaller = jaxbContext.createMarshaller(); // 关键:禁用JAXB自带的格式化,避免它插入空白 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false); // 设置编码和其他必要属性 marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); // 加载XSLT转换器 TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(new StreamSource("path/to/your-xslt.xsl")); // 先把JAXB对象序列化为DOM,再用XSLT处理输出 DOMResult domResult = new DOMResult(); marshaller.marshal(yourModelObject, domResult); // 执行转换并输出到目标流 transformer.transform(new DOMSource(domResult.getNode()), new StreamResult(yourOutputStream));
关键注意事项
- 一定要确保XSLT中
suppress-indentation的命名空间,和XML里Signature元素的命名空间完全一致(默认是http://www.w3.org/2000/09/xmldsig#) - 测试时可以对比签名前后的XML字符(用文本编辑器查看原始字节),确保Signature部分(包括标签前后的空白)和签名生成时完全一致
- 这个方案是通用适配的,不管Signature前面的元素是birth还是其他任何元素,只要是它的前一个兄弟元素,都会被处理成同一行
内容的提问来源于stack exchange,提问作者Chr3is
相关产品推荐
相关产品推荐

