使用Apache Santuario XML Security构建XAdES签名器时遭遇ReferenceNotInitializedException异常的求助
我正在尝试用Java结合Apache Santuario XML Security库构建一个XAdES签名器,但在签名文档时一直抛出以下异常:
Exception in thread "main" org.apache.xml.security.signature.ReferenceNotInitializedException: Cannot resolve element with ID IHEManifest
Original Exception was org.apache.xml.security.signature.ReferenceNotInitializedException: Cannot resolve element with ID IHEManifest
Original Exception was org.apache.xml.security.signature.ReferenceNotInitializedException: Cannot resolve element with ID IHEManifest
Original Exception was org.apache.xml.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID IHEManifest
我已经排查的内容:
- 已设置Manifest元素的ID属性:
manifestElem.setIdAttribute("Id", true); - 元素
#IHEManifest在签名操作前已存在于DOM中 - 依赖配置(pom.xml):
<dependency> <groupId>org.apache.santuario</groupId> <artifactId>xmlsec</artifactId> <version>1.5.3</version> </dependency>
我的构建XML和签名方法代码:
public static Document sign(byte[] bytes, String signatureId, String documentId, String keystorePath, String keystorePassword) throws Exception { // Compute SHA1 digest of the document MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); byte[] hashBytes = sha1.digest(bytes); StringBuilder hashHex = new StringBuilder(); for (byte b : hashBytes) hashHex.append(String.format("%02x", b)); String base64Hash = Base64.getEncoder().encodeToString(hashHex.toString().getBytes("UTF-8")); // Load KeyStore and get PrivateKey and Certificate KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(new java.io.FileInputStream(keystorePath), keystorePassword.toCharArray()); String alias = ks.aliases().nextElement(); PrivateKey privateKey = (PrivateKey) ks.getKey(alias, keystorePassword.toCharArray()); X509Certificate cert = (X509Certificate) ks.getCertificate(alias); // Create empty XML document Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); // Create Signature element XMLSignature sig = new XMLSignature(doc, null, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1); sig.getElement().setAttribute("Id", signatureId); // Manifest element (inside ds:Object) Element manifestElem = doc.createElementNS(org.apache.xml.security.utils.Constants.SignatureSpecNS, "ds:Manifest"); manifestElem.setAttribute("Id", "IHEManifest"); manifestElem.setIdAttribute("Id", true); // Reference inside Manifest Element manifestRef = doc.createElementNS(org.apache.xml.security.utils.Constants.SignatureSpecNS, "ds:Reference"); manifestRef.setAttribute("URI", documentId); Element digestMethod = doc.createElementNS(org.apache.xml.security.utils.Constants.SignatureSpecNS, "ds:DigestMethod"); digestMethod.setAttribute("Algorithm", org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1); Element digestValue = doc.createElementNS(org.apache.xml.security.utils.Constants.SignatureSpecNS, "ds:DigestValue"); digestValue.setTextContent(base64Hash); manifestRef.appendChild(digestMethod); manifestRef.appendChild(digestValue); manifestElem.appendChild(manifestRef); // KeyInfo: X509Data and KeyValue KeyInfo ki = sig.getKeyInfo(); X509Data x509Data = new X509Data(doc); x509Data.addCertificate(cert); ki.add(x509Data); KeyValue keyValue = new KeyValue(doc, cert.getPublicKey()); ki.add(keyValue); // ds:Object with XAdES properties Element objectElem = doc.createElementNS(org.apache.xml.security.utils.Constants.SignatureSpecNS, "ds:Object"); objectElem.setAttribute("Id", "object"); // XAdES QualifyingProperties Element qualifyingProperties = doc.createElementNS("http://uri.etsi.org/01903/v1.1.1#", "QualifyingProperties"); qualifyingProperties.setAttribute("Target", "#" + signatureId); // SignedProperties Element signedProperties = doc.createElementNS("http://uri.etsi.org/01903/v1.1.1#", "SignedProperties"); signedProperties.setAttribute("Id", "SignedPropertiesId"); // SignedSignatureProperties Element signedSignatureProperties = doc.createElementNS("http://uri.etsi.org/01903/v1.1.1#", "SignedSignatureProperties"); // SigningTime Element signingTime = doc.createElementNS("http://uri.etsi.org/01903/v1.1.1#", "SigningTime"); signingTime.setTextContent(java.time.Instant.now().toString()); signedSignatureProperties.appendChild(signingTime); // SigningCertificate Element signingCertificate = doc.createElementNS("http://uri.etsi.org/01903/v1.1.1#", "SigningCertificate"); Element certElem = doc.createElementNS("http://uri.etsi.org/01903/v1.1.1#", "Cert"); // CertDigest Element certDigest = doc.createElementNS("http://uri.etsi.org/01903/v1.1.1#", "CertDigest"); // 原代码此处截断,后续未完成CertDigest的子元素创建 }
可能的解决方案及排查方向
1. 最核心问题:Manifest元素未加入DOM树结构
你的代码中创建了manifestElem、objectElem等节点,但没有将它们添加到签名结构的DOM树中,导致manifestElem是游离的DOM节点,签名过程中根本找不到它。
需要补充以下代码,将节点按层级加入DOM树:
// 1. 将Manifest添加到Object元素 objectElem.appendChild(manifestElem); // 2. 完成XAdES元素的层级拼接(以SigningCertificate为例) Element digestMethodCert = doc.createElementNS("http://uri.etsi.org/01903/v1.1.1#", "DigestMethod"); digestMethodCert.setAttribute("Algorithm", org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1); certDigest.appendChild(digestMethodCert); certElem.appendChild(certDigest); signingCertificate.appendChild(certElem); signedSignatureProperties.appendChild(signingCertificate); signedProperties.appendChild(signedSignatureProperties); qualifyingProperties.appendChild(signedProperties); objectElem.appendChild(qualifyingProperties); // 3. 将Object元素添加到Signature元素 sig.getElement().appendChild(objectElem); // 4. 将Signature元素添加到文档根节点 doc.appendChild(sig.getElement());
2. 升级依赖版本
你使用的xmlsec 1.5.3是2013年的旧版本,存在不少DOM ID解析、XAdES支持的bug。建议升级到稳定新版本,比如:
<dependency> <groupId>org.apache.santuario</groupId> <artifactId>xmlsec</artifactId> <version>2.4.3</version> </dependency>
新版本对ID属性的识别、DOM树的处理更可靠,能避免很多旧版本的兼容性问题。
3. 显式注册ID属性(旧版本兼容)
如果暂时无法升级依赖,可尝试显式告诉Santuario识别Id属性为ID类型:
XMLSecurityManager securityManager = sig.getXMLSecurityManager(); // 注册签名命名空间下的Id属性为ID属性 securityManager.addIdNS(org.apache.xml.security.utils.Constants.SignatureSpecNS, "Id");
4. 检查Reference的URI格式
如果documentId是指向内部元素的ID,需要以#开头(比如#targetDocId);如果是外部文档,需确保ResourceResolver能正确解析,但当前异常的核心原因还是Manifest节点未加入DOM树。
先补充DOM节点的层级拼接代码,再尝试升级依赖,应该能解决当前的异常问题。

