You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 02:36:31