Apache Santuario 构建并运行示例应用指南
Apache Santuario 3.0.6 构建并运行示例应用指南:从零创建 Maven 工程、完整 pom.xml、RSA-SHA256 enveloped 签名/验签/篡改验签失败三段示例源码、运行命令与真实回显(signature valid: true、篡改后 false)、jar 直跑方案与 JVM 堆限制验证,全程实测。
Apache Santuario 构建并运行示例应用指南
目标:从零构建并运行一个基于 Apache Santuario 3.0.6(制品名
xmlsec)的 XML 签名最小应用,覆盖完整闭环:enveloped 签名 → 验签成功(signature valid: true)→ 篡改金额后验签失败(signature valid: false)。给出完整 pom.xml、示例数据、三段示例源码、编译运行命令与真实回显,并附 jar 直跑方案。所有命令与输出均在 Ubuntu 22.04.5 + OpenJDK 17 环境实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS
- Java:OpenJDK 17.0.20.1
- 构建工具:Apache Maven 3.6.3
- Santuario 版本:xmlsec 3.0.6(3.x 线最新稳定版,Maven Central 实查;4.0.4 属 4.x 新主线)
- 示例工程目录:
/root/xmlsec-demo/ - 演示账号:root
关键版本回显:
$ java -version
openjdk version "17.0.20.1" 2026-08-18
OpenJDK Runtime Environment (build 17.0.20.1+1-1-22.04-Ubuntu)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1-22.04-Ubuntu, mixed mode, sharing)
$ mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en_US, platform encoding: ANSI_X3.4-1968
OS name: "linux", version: "6.8.0-138-generic", arch: "amd64", family: "unix"
二、前置条件
-
已按《Apache Santuario 安装指南》完成 JDK 17 与 Maven 安装,
java -version、mvn -version可用。 -
已安装
curl、wget(方案 B 需要):apt-get install -y -qq curl wget回显收尾:
Processing triggers for libc-bin (2.35-0ubuntu3.15) ... -
xmlsec 依赖将以 Maven 方式引入(坐标
org.apache.santuario:xmlsec:3.0.6),Maven Central 直连可用。
三、工程结构
标准 Maven 布局,1 个 pom、3 个示例类、1 份待签名数据:
xmlsec-demo/
├── pom.xml
├── invoice.xml
└── src/main/java/org/example/
├── SignExample.java
├── ValidateExample.java
└── ValidateTamperedExample.java
创建目录:
mkdir -p /root/xmlsec-demo/src/main/java/org/example
三个类的职责:
| 类 | 职责 |
|---|---|
| SignExample | 生成 RSA 2048 密钥对,对 invoice.xml 做 enveloped 签名,输出 signed.xml |
| ValidateExample | 读取公钥对 signed.xml 验签(开启 secure validation) |
| ValidateTamperedExample | 把金额 12800.00 篡改为 99999.00 后验签,演示失败场景 |
四、完整 pom.xml
写入 /root/xmlsec-demo/pom.xml。exec-maven-plugin 用于以 mvn exec:java 直接运行主类:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>xmlsec-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
<version>3.0.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
首次编译验证(会从 Maven Central 下载依赖):
$ cd /root/xmlsec-demo && mvn clean compile
...
[INFO] Compiling 3 source files to /root/xmlsec-demo/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:25 min
[INFO] Finished at: 2026-10-12T02:42:14Z
[INFO] ------------------------------------------------------------------------
五、示例数据与源码
5.1 待签名数据 invoice.xml
在工程根目录 /root/xmlsec-demo/ 创建 invoice.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Invoice id="INV-2026-001">
<Customer>Acme Corp</Customer>
<Amount currency="CNY">12800.00</Amount>
<Items>
<Item id="A-1001">
<Name>XML Security Handbook</Name>
<Quantity>2</Quantity>
</Item>
<Item id="A-1002">
<Name>Applied Cryptography</Name>
<Quantity>1</Quantity>
</Item>
</Items>
</Invoice>
5.2 签名类 SignExample.java
创建 /root/xmlsec-demo/src/main/java/org/example/SignExample.java。要点:显式传入 Santuario 提供者 XMLDSigRI;对整个文档(URI="")做 enveloped 转换 + SHA256 摘要;RSA-SHA256 签名;密钥对落盘为 PEM:
package org.example;
import org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Base64;
import java.util.Collections;
public class SignExample {
public static void main(String[] args) throws Exception {
// 1) 生成 RSA 2048 密钥对并落盘(PKCS#8 私钥 / X.509 公钥,PEM 编码)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
Base64.Encoder b64 = Base64.getMimeEncoder(64, "\n".getBytes());
Files.write(Paths.get("private.key"),
("-----BEGIN PRIVATE KEY-----\n" + b64.encodeToString(kp.getPrivate().getEncoded())
+ "\n-----END PRIVATE KEY-----\n").getBytes());
Files.write(Paths.get("public.key"),
("-----BEGIN PUBLIC KEY-----\n" + b64.encodeToString(kp.getPublic().getEncoded())
+ "\n-----END PUBLIC KEY-----\n").getBytes());
System.out.println("[Info] RSA 2048 keypair generated: private.key / public.key");
// 2) 解析待签名 XML(namespace aware)
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
org.w3c.dom.Document doc = dbf.newDocumentBuilder().parse(new File("invoice.xml"));
// 3) 显式注册 Apache Santuario 的 JSR-105 提供者 XMLDSigRI
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM", new XMLDSigRI());
System.out.println("[Info] XMLSignatureFactory provider: " + fac.getProvider().getName()
+ " - " + fac.getProvider().getInfo());
// 4) Reference:对整个文档(URI="")做 enveloped 转换 + SHA256 摘要
DigestMethod dm = fac.newDigestMethod(DigestMethod.SHA256, null);
Transform tf = fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null);
Reference ref = fac.newReference("", dm, Collections.singletonList(tf), null, null);
// 5) SignedInfo:inclusive c14n + RSA-SHA256 签名算法
SignedInfo si = fac.newSignedInfo(
fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null),
Collections.singletonList(ref));
// 6) KeyInfo:携带 RSA 公钥(KeyValue)
KeyInfoFactory kif = fac.getKeyInfoFactory();
KeyValue kv = kif.newKeyValue(kp.getPublic());
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
// 7) 构造签名对象并签入文档(签名作为根元素最后一个子节点)
XMLSignature signature = fac.newXMLSignature(si, ki);
DOMSignContext dsc = new DOMSignContext(kp.getPrivate(), doc.getDocumentElement());
signature.sign(dsc);
System.out.println("[Info] document signed, Signature appended to root element");
// 8) 序列化到 signed.xml(不启用 indent,保证输出与被签内容字节级一致)
Transformer t = TransformerFactory.newInstance().newTransformer();
t.transform(new DOMSource(doc), new StreamResult(new FileOutputStream("signed.xml")));
System.out.println("[Info] signed document written to signed.xml");
}
}
5.3 验签类 ValidateExample.java
创建 /root/xmlsec-demo/src/main/java/org/example/ValidateExample.java。要点:开启 secure validation;核心验证通过后逐 Reference 输出有效性:
package org.example;
import org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Iterator;
public class ValidateExample {
public static void main(String[] args) throws Exception {
// 1) 读取公钥(PEM -> DER -> PublicKey)
String pem = new String(Files.readAllBytes(Paths.get("public.key")))
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
PublicKey publicKey = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(pem)));
// 2) 解析待验证文档并定位 Signature 节点
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
org.w3c.dom.Document doc = dbf.newDocumentBuilder().parse(new File("signed.xml"));
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM", new XMLDSigRI());
org.w3c.dom.NodeList sigs = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (sigs.getLength() == 0) {
throw new IllegalStateException("no Signature element found");
}
DOMValidateContext vc = new DOMValidateContext(publicKey, sigs.item(0));
// 3) 开启安全校验(secure validation)
vc.setProperty("org.apache.jcp.xml.dsig.secureValidation", Boolean.TRUE);
// 4) unmarshal + 验签
XMLSignature signature = fac.unmarshalXMLSignature(vc);
boolean coreValid = signature.validate(vc);
System.out.println("signature valid: " + coreValid);
if (coreValid) {
Iterator<?> it = signature.getSignedInfo().getReferences().iterator();
for (int j = 0; it.hasNext(); j++) {
boolean refValid = ((Reference) it.next()).validate(vc);
System.out.println("ref[" + j + "] validity status: " + refValid);
}
}
}
}
5.4 篡改验签类 ValidateTamperedExample.java
创建 /root/xmlsec-demo/src/main/java/org/example/ValidateTamperedExample.java。要点:解析 signed.xml 后把 <Amount> 文本改为 99999.00,另存 tampered.xml,再对篡改后的文档验签:
package org.example;
import org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class ValidateTamperedExample {
public static void main(String[] args) throws Exception {
// 1) 读取公钥(PEM -> DER -> PublicKey)
String pem = new String(Files.readAllBytes(Paths.get("public.key")))
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
PublicKey publicKey = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(pem)));
// 2) 解析 signed.xml 并篡改 <Amount> 金额
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
org.w3c.dom.Document doc = dbf.newDocumentBuilder().parse(new File("signed.xml"));
org.w3c.dom.Node amount = doc.getElementsByTagName("Amount").item(0);
System.out.println("[Info] Amount before tamper: " + amount.getTextContent());
amount.setTextContent("99999.00");
System.out.println("[Info] Amount after tamper: 99999.00");
// 3) 篡改后的文档另存 tampered.xml
Transformer t = TransformerFactory.newInstance().newTransformer();
t.transform(new DOMSource(doc), new StreamResult(new FileOutputStream("tampered.xml")));
// 4) 对篡改后的文档验签
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM", new XMLDSigRI());
org.w3c.dom.NodeList sigs = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
DOMValidateContext vc = new DOMValidateContext(publicKey, sigs.item(0));
vc.setProperty("org.apache.jcp.xml.dsig.secureValidation", Boolean.TRUE);
XMLSignature signature = fac.unmarshalXMLSignature(vc);
boolean coreValid = signature.validate(vc);
System.out.println("signature valid: " + coreValid);
}
}
六、编译与运行
先编译,再依次运行三个主类。运行前把示例进程堆内存限制在 256MB–512MB:
cd /root/xmlsec-demo
mvn -q clean compile
export MAVEN_OPTS="-Xms256m -Xmx512m"
6.1 步骤一:签名(SignExample)
$ cd /root/xmlsec-demo && mvn -q compile exec:java -Dexec.mainClass=org.example.SignExample
[Info] RSA 2048 keypair generated: private.key / public.key
[Info] XMLSignatureFactory provider: ApacheXMLDSig - Apache Santuario XMLDSig (DOM XMLSignatureFactory; DOM KeyInfoFactory; C14N 1.0, C14N 1.1, Exclusive C14N, Base64, Enveloped, XPath, XPath2, XSLT TransformServices)
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[Info] document signed, Signature appended to root element
[Info] signed document written to signed.xml
运行后在工程目录生成 private.key、public.key、signed.xml。注意:每次运行 SignExample 都会重新生成密钥对并覆盖这两个密钥文件,如需复用密钥请先备份。
6.2 步骤二:验签(ValidateExample)
$ mvn -q compile exec:java -Dexec.mainClass=org.example.ValidateExample
signature valid: true
ref[0] validity status: true
6.3 步骤三:篡改后验签(ValidateTamperedExample)
$ mvn -q compile exec:java -Dexec.mainClass=org.example.ValidateTamperedExample
[Info] Amount before tamper: 12800.00
[Info] Amount after tamper: 99999.00
signature valid: false
七、运行结果解读
7.1 签名产物 signed.xml
cat signed.xml 实测全文(Signature 已追加到根元素 Invoice 末尾):
$ cat signed.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?><Invoice id="INV-2026-001">
<Customer>Acme Corp</Customer>
<Amount currency="CNY">12800.00</Amount>
<Items>
<Item id="A-1001">
<Name>XML Security Handbook</Name>
<Quantity>2</Quantity>
</Item>
<Item id="A-1002">
<Name>Applied Cryptography</Name>
<Quantity>1</Quantity>
</Item>
</Items>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><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>i5VbidKnNm0Vwc9nYeLa7sfwXRJZmaIHCD2QU/nKblA=</DigestValue></Reference></SignedInfo><SignatureValue>aRTStLdQ9TXnxUqbQfPPFAg0S6IJ3KJa6p9s8bV2vDFDOxCZBpxWIsXWXyNG56s05SpqSvZrIJDg
DgaTfTY9HxLtzWZnaIoJ4gFTq3USiWrY6Lti+NmJl/fpItZjBAe2a2YI8Xg6ikcnvEkwU5HScAdg
yf9ipNYywIAwowVfvUjmCUTazIU5Y1Mm8oFrRGmnV/MLtlUiVpsHw6NK21/J4QEp0gdmdde82fHQ
jhSzeIsACq31XE3h7tqvB5KfadKzPRUddOLXsHjJMqvpaEvelAHXIh/J+LJM18volojzJeRNuBTH
3QLS5KjmsDwrjZ0/O0Zs+cUYB5Sfp3/Fv8Nj4w==</SignatureValue><KeyInfo><KeyValue><RSAKeyValue><Modulus>xObYGfy7IQvO3IWFxLDoGLoaUlhTCD+ZfN3WAhIg3bXAnoj0FZvSgQMeEDL9z2ul70K2oa9+bDiM
ggRka7dN0UIkWnr0MXRqLLVEMzwy24sk/8yMFtTiLpFEyOM/sg4epI5E/Xq/sig9EhR1cl8yumYv
L7w0IkhaGr3jFKS5G0XOPJPuULqGi44QwHPDERov74jryUHCVVUULX5XfWgMJXHvGUbHFJPFXPse
WB4Xumqxq02uO+197g9YzsqcXY8OoKvnK2T0IWOo00GAa+uUz7mpB5n5EIJiOrgC15zi2LZzDcS5
+qo1jzBYTpkwRGVOZdqe5sC0SKgHBldrUWZexQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue></KeyInfo></Signature></Invoice>
结构要点:
CanonicalizationMethod:inclusive c14n(http://www.w3.org/TR/2001/REC-xml-c14n-20010315),与代码中CanonicalizationMethod.INCLUSIVE对应。SignatureMethod:rsa-sha256(http://www.w3.org/2001/04/xmldsig-more#rsa-sha256),与SignatureMethod.RSA_SHA256对应。Reference URI=""配合enveloped-signature转换:摘要计算时剔除 Signature 自身,避免自引用死循环。DigestMethod:SHA256(http://www.w3.org/2001/04/xmlenc#sha256);DigestValue是文档(除 Signature 外)规范化后的摘要。SignatureValue:用 RSA 私钥对 SignedInfo 签名后的值;KeyInfo/KeyValue/RSAKeyValue内嵌了公钥模数与指数,验签方可直接取用。
7.2 验签输出
signature valid: true 表示核心验证(引用验证 + 签名验证)全部通过:文档未被改动,且 SignatureValue 可用公钥验证成立。ref[0] validity status: true 表示第 0 个 Reference 的摘要验证通过。
7.3 篡改验签输出
金额从 12800.00 改为 99999.00 后,文档摘要与 DigestValue 不再匹配,signature valid: false。这正是 XML 签名的防篡改语义:任何对被覆盖内容(URI="" 覆盖整个文档)的改动都会导致验签失败。
八、方案 B:直接使用 jar 包运行(可选)
无 Maven 环境时,可用 javac/java 配合 5 个 jar 直接编译运行(jar 清单与下载见《Apache Santuario 安装指南》)。
8.1 编译示例类
javac -encoding UTF-8 -cp '/opt/xmlsec/lib/*' -d /opt/xmlsec/classes /root/xmlsec-demo/src/main/java/org/example/SignExample.java /root/xmlsec-demo/src/main/java/org/example/ValidateExample.java
编译成功无任何输出。
8.2 运行签名与验签
把 invoice.xml 复制到独立工作目录后运行:
cp /root/xmlsec-demo/invoice.xml /root/xmlsec-cp-test/
cd /root/xmlsec-cp-test
java -cp '/opt/xmlsec/classes:/opt/xmlsec/lib/*' org.example.SignExample
java -cp '/opt/xmlsec/classes:/opt/xmlsec/lib/*' org.example.ValidateExample
实测回显与 Maven 方式一致——签名:
$ cd /root/xmlsec-cp-test && java -cp '/opt/xmlsec/classes:/opt/xmlsec/lib/*' org.example.SignExample
[Info] RSA 2048 keypair generated: private.key / public.key
[Info] XMLSignatureFactory provider: ApacheXMLDSig - Apache Santuario XMLDSig (DOM XMLSignatureFactory; DOM KeyInfoFactory; C14N 1.0, C14N 1.1, Exclusive C14N, Base64, Enveloped, XPath, XPath2, XSLT TransformServices)
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[Info] document signed, Signature appended to root element
[Info] signed document written to signed.xml
验签:
$ cd /root/xmlsec-cp-test && java -cp '/opt/xmlsec/classes:/opt/xmlsec/lib/*' org.example.ValidateExample
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
signature valid: true
ref[0] validity status: true
8.3 JVM 堆参数生效验证
用 Maven 生成依赖 classpath 文件后以 -Xms256m -Xmx512m 直跑,并用 jps -v 确认:
$ mvn -q dependency:build-classpath -Dmdep.outputFile=cp.txt
$ head -c 200 cp.txt
/root/.m2/repository/org/apache/santuario/xmlsec/3.0.6/xmlsec-3.0.6.jar:/root/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/root/.m2/repository/jakarta/xml/bind/jakarta.xml.bind-api/
$ java -Xms256m -Xmx512m -cp target/classes:$(cat cp.txt) org.example.ValidateExample &
$ jps -v | grep -F 'Xmx512m'
6855 ValidateExample -Xms256m -Xmx512m
jps -v 输出中的 -Xms256m -Xmx512m 即堆参数生效标记(进程号以实际为准),该次运行最终输出同样为 signature valid: true 与 ref[0] validity status: true。
九、注意事项
⚠️ 注意:
DocumentBuilderFactory必须调用setNamespaceAware(true),否则无法按命名空间定位 Signature 节点,验签会失败。- 序列化 signed.xml 时不要启用 indent:缩进会改变文档字节内容,破坏摘要一致性,导致验签失败。
- SignExample 每次运行都会覆盖密钥文件与 signed.xml;验签必须使用签名同一次运行生成的公钥。
- 生产验签务必开启 secure validation(
org.apache.jcp.xml.dsig.secureValidation置 TRUE),以启用算法白名单与 Transform 数量限制,详见《Apache Santuario 配置参数详解》。- 示例用 RSA 2048 + SHA256 组合,是 secure validation 下的安全默认选择;MD5 等弱算法会被拒绝。
十、常见问题
Q1:provider 回显是 XMLDSig 而不是 ApacheXMLDSig?
说明 XMLSignatureFactory.getInstance("DOM") 未显式传入 Santuario 提供者,解析到了 JDK 内置实现。按示例写法 getInstance("DOM", new org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI()) 即可,诊断过程见《Apache Santuario 常用工具与 API 详解》。
Q2:signature valid: false 的常见原因有哪些?
按命中率排查:文档在签名后被修改(含空格、换行、缩进变化);签名与验签使用了不同密钥对;序列化时启用了 indent;解析时未开启 namespace aware。
Q3:SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" 报错吗?
不是错误。xmlsec 依赖 slf4j-api 但未绑定日志实现,SLF4J 回退到 NOP 日志,不影响签名与验签功能。