如何在Java数字签名中添加XPATH转换算法
Java XML数字签名中添加XPATH转换的正确实现
我已经在XML数字签名中成功添加了Enveloped转换,代码如下:
List<Transform> transforms = new LinkedList<>(); Transform envelopedTransform = xmlSignatureFactory.newTransform(CanonicalizationMethod.ENVELOPED, (TransformParameterSpec) null); transforms.add(envelopedTransform);
尝试添加XPATH转换时用了这段代码,但抛出异常:
Transform xpath = xmlSignatureFactory.newTransform(CanonicalizationMethod.XPATH, (TransformParameterSpec) null); transforms.add(xpath);
期望生成的XML转换结构是:
<ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116"> <ds:XPath>not(ancestor-or-self::eb:TraceHeaderList or ancestor-or-self::eb:Via)</ds:XPath> </ds:Transform> </ds:Transforms>
问题根源
添加XPATH转换时不能直接传入null作为TransformParameterSpec,因为XPATH转换要求必须指定要执行的XPath表达式;如果XPath中使用了自定义命名空间前缀(比如示例中的eb),还需要绑定前缀对应的命名空间URI,否则解析XPath时会报错。
解决代码
修改XPATH转换的创建逻辑,使用XPathTransformParameterSpec配置参数:
// 1. 定义目标XPath表达式 String xpathExpr = "not(ancestor-or-self::eb:TraceHeaderList or ancestor-or-self::eb:Via)"; // 2. 配置命名空间映射(替换为你实际使用的eb命名空间URI) Map<String, String> namespaceMap = new HashMap<>(); namespaceMap.put("eb", "http://你的eb命名空间URI"); // 3. 创建带命名空间支持的XPath实例 XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpathObj = xPathFactory.newXPath(); xpathObj.setNamespaceContext(new NamespaceContext() { @Override public String getNamespaceURI(String prefix) { if ("eb".equals(prefix)) { return namespaceMap.get(prefix); } return XMLConstants.NULL_NS_URI; } @Override public String getPrefix(String namespaceURI) { return namespaceMap.entrySet().stream() .filter(entry -> entry.getValue().equals(namespaceURI)) .map(Map.Entry::getKey) .findFirst() .orElse(null); } @Override public Iterator<String> getPrefixes(String namespaceURI) { return namespaceMap.entrySet().stream() .filter(entry -> entry.getValue().equals(namespaceURI)) .map(Map.Entry::getKey) .iterator(); } }); // 4. 编译XPath表达式并创建转换参数 XPathExpression xpathExpression = xpathObj.compile(xpathExpr); XPathTransformParameterSpec xpathParamSpec = new XPathTransformParameterSpec(xpathExpression); // 5. 创建XPATH Transform并添加到转换列表 Transform xpathTransform = xmlSignatureFactory.newTransform(Transform.XPATH, xpathParamSpec); transforms.add(xpathTransform);
完整修改后的签名代码
替换原代码中创建transforms的部分,完整代码如下:
JSONObject jsonObject = new JSONObject(); // 创建DOM XMLSignatureFactory实例 XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM"); DigestMethod digestMethod = xmlSignatureFactory.newDigestMethod(DigestMethod.SHA1, null); List<Transform> transforms = new LinkedList<>(); // 添加Enveloped转换 Transform envelopedTransform = xmlSignatureFactory.newTransform(CanonicalizationMethod.ENVELOPED, (TransformParameterSpec) null); transforms.add(envelopedTransform); // 添加XPATH转换 String xpathExpr = "not(ancestor-or-self::eb:TraceHeaderList or ancestor-or-self::eb:Via)"; Map<String, String> namespaceMap = new HashMap<>(); // 替换为实际的eb命名空间URI namespaceMap.put("eb", "http://example.com/ebnamespace"); XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpathObj = xPathFactory.newXPath(); xpathObj.setNamespaceContext(new NamespaceContext() { @Override public String getNamespaceURI(String prefix) { if ("eb".equals(prefix)) { return namespaceMap.get(prefix); } return XMLConstants.NULL_NS_URI; } @Override public String getPrefix(String namespaceURI) { return namespaceMap.entrySet().stream() .filter(entry -> entry.getValue().equals(namespaceURI)) .map(Map.Entry::getKey) .findFirst() .orElse(null); } @Override public Iterator<String> getPrefixes(String namespaceURI) { return namespaceMap.entrySet().stream() .filter(entry -> entry.getValue().equals(namespaceURI)) .map(Map.Entry::getKey) .iterator(); } }); try { XPathExpression xpathExpression = xpathObj.compile(xpathExpr); XPathTransformParameterSpec xpathParamSpec = new XPathTransformParameterSpec(xpathExpression); Transform xpathTransform = xmlSignatureFactory.newTransform(Transform.XPATH, xpathParamSpec); transforms.add(xpathTransform); } catch (XPathExpressionException e) { e.printStackTrace(); // 按需处理异常 } // 创建Reference实例 Reference reference = xmlSignatureFactory.newReference("", digestMethod, transforms, null, null); List<Reference> references = new ArrayList<>(); references.add(reference); // 创建SignedInfo SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo( xmlSignatureFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null), xmlSignatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null), references); // 加载密钥库及签名密钥 KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new FileInputStream("keyStore.p12"), "123456".toCharArray()); KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry( "alias", new KeyStore.PasswordProtection("123456".toCharArray())); X509Certificate cert = (X509Certificate) keyEntry.getCertificate(); // 创建KeyInfo KeyInfoFactory keyInfoFactory = xmlSignatureFactory.getKeyInfoFactory(); List x509Content = new ArrayList(); x509Content.add(cert.getSubjectX500Principal().getName()); x509Content.add(cert); X509Data x509Data = keyInfoFactory.newX509Data(x509Content); KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data)); // 加载待签名的XML文档 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); documentBuilderFactory.setNamespaceAware(true); Resource resource = new FileSystemResource(resourceFileLocation.concat("ase.xml")); Document document = documentBuilderFactory.newDocumentBuilder().parse(resource.getFile()); // 创建签名上下文 DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), document.getDocumentElement()); // 创建并执行签名 XMLSignature signature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo); try { signature.sign(dsc); } catch (XMLSignatureException e) { e.printStackTrace(); // 按需处理签名异常 } // 输出签名后的XML OutputStream os = new FileOutputStream("signedEbXml.xml"); TransformerFactory tf = TransformerFactory.newInstance(); Transformer trans = tf.newTransformer(); trans.transform(new DOMSource(document), new StreamResult(os)); os.flush(); os.close(); jsonObject.put("response", CommonConstants.RESPONSE); return jsonObject;
注意事项
- 必须将代码中
eb前缀对应的命名空间URI替换为你实际使用的URI,否则XPath无法正确匹配目标节点 - 需处理
XPathExpressionException和XMLSignatureException等异常,避免程序崩溃 - 原代码中重复创建了
envelopReference和reference,已优化为只保留一个有效的Reference实例
内容的提问来源于stack exchange,提问作者Tharindu Rukshan
相关产品推荐
相关产品推荐

