zoukankan      html  css  js  c++  java
  • XML签名Cannot resolve element with ID XXXX 解决方案

    最近同银行做接口联调,需要对XML文件做加签和解签操作,本地的开发环境是Mac 10.10,JDK的版本是1.6.0.65。小小的一段加签代码,一直报错,却久久也找不到解决方法,网上的资料非常少,错误记录如下:

     1 javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID FIQReq
     2 Exception in thread "main" java.lang.RuntimeException: javax.xml.crypto.dsig.XMLSignatureException: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID FIQReq
     3 at util.xml.XMLSigner.sign(XMLSigner.java:111)
     4 at test.TestSign.main(TestSign.java:34)
     5 Caused by: javax.xml.crypto.dsig.XMLSignatureException: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID FIQReq
     6 at org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:412)
     7 at org.jcp.xml.dsig.internal.dom.DOMReference.digest(DOMReference.java:338)
     8 at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.digestReference(DOMXMLSignature.java:471)
     9 at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.sign(DOMXMLSignature.java:367)
    10 at util.xml.XMLSigner.sign(XMLSigner.java:108)
    11 ... 1 more
    12 Caused by: javax.xml.crypto.URIReferenceException: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID FIQReq
    13 at org.jcp.xml.dsig.internal.dom.DOMURIDereferencer.dereference(DOMURIDereferencer.java:124)
    14 at org.jcp.xml.dsig.internal.dom.DOMReference.dereference(DOMReference.java:404)
    15 ... 5 more
    16 Caused by: com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID FIQReq
    17 at com.sun.org.apache.xml.internal.security.utils.resolver.implementations.ResolverFragment.engineResolve(ResolverFragment.java:90)
    18 at com.sun.org.apache.xml.internal.security.utils.resolver.ResourceResolver.resolve(ResourceResolver.java:283)
    19 at org.jcp.xml.dsig.internal.dom.DOMURIDereferencer.dereference(DOMURIDereferencer.java:117)
    20 ... 6 more

    唯一一个说的比较对的上的就是这篇Bug记录了:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8017171

    产生该问题的原因就是无法根据XML文档节点的ID属性定位到对应的节点,所以在加签的时候会提示不能解析该ID,那么怎样才能让他可以根据我节点的ID,解析并定位到对应的节点呢,让我们先看下我项目中的代码:

    首先是要进行加签的XML文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <SoEv>
        <Message id="54b8db6c24574e23423********">
            <FIQReq id="FIQReq">
                <version>1.0.1</version>
                <instId>9550811********</instId>
                <certId>0001</certId>
                <channelDate>20150608</channelDate>
                <beginPos>0</beginPos>
                <pageNum>10</pageNum>
            </FIQReq>
        </Message>
    </SoEv>

    加签的代码:

     1 public void sign(Document doc, PrivateKey privateKey, String referenceId, Node signNode) {
     2         if(!isInit){
     3             throw new RuntimeException("XMLSigner is not init !");
     4         }
     5         try{
     6             //创建 <Reference> 元素,引用指定ID的节点,<Signature> 元素不会被计算在内
     7             Reference refToRootDoc = signFactory.newReference("#"+referenceId,
     8                     sha1DigMethod, Collections.singletonList(envelopedTransform), null, null);
     9             //创建 <SignedInfo> 元素
    10             SignedInfo signedInfo = signFactory.newSignedInfo(c14nWithCommentMethod,
    11                     rsa_sha1SigMethod, Collections.singletonList(refToRootDoc));
    12             //创建签名实例
    13             XMLSignature signature = signFactory.newXMLSignature(signedInfo, null);
    14             //创建签名上下文,在指定节点生成
    15             DOMSignContext dsc = new DOMSignContext(privateKey, signNode);
    16             //设置签名域命名空间前缀
    17             if (defaultNamespacePrefix != null) {
    18                 dsc.setDefaultNamespacePrefix(defaultNamespacePrefix);21             }
    22             //签名
    23             signature.sign(dsc);
    24         }catch(Exception e){
    25             System.out.println(e.getMessage());
    26             throw new RuntimeException(e);
    27         }
    28     }

    方法外传入需要做加签处理的XML文档树,私钥,需要加签节点的ID,这里就是上面XML中的FIQReq节点的ID内容(FIQReq),还有一个节点就是加签所生成的SignedInfo的父元素,也就是要把SignedInfo节点写在那个节点下面。

    这样执行这段代码就会报如上所示的错误代码。

    在上面介绍的http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8017171中,有这样三条解决建议:

    There are 3 potential workarounds that you can apply:
    
    1. Use a validating schema which will register the elements with ID references.
    
    2. Register the ID elements with the DOMValidateContext.setIdAttributeNS method before validating the signature
    
    3. Implement a custom URIDereferencer which can find these references and override the builtin URIDereferencer with the DOMValidateContext.setURIDereferencer method.

    但是看起来还不是特别直观不能一下子解决该问题。但是给出了解决问题的方向,而我解决该问题的着眼点就在于第二条:

    2. Register the ID elements with the DOMValidateContext.setIdAttributeNS method before validating the signature

    上面介绍的加签代码虽然没有DOMValidateContext对象的参与,但是有DOMSignContext,他也有setIdAttributeNS方法,所以我们就是要在这个方法上做文章:

    在上面的加签代码上加入如下两条代码即可解决该问题:

    Element element = (Element)doc.getElementsByTagName(referenceId).item(0);
    dsc.setIdAttributeNS(element, null, "id");

    加入这两行代码后的整体代码变为:

    /**
         * XML签名
         * <p>签名使用内嵌方式,生成在指定节点</p>
         * @param doc XML文档
         * @param privateKey 私钥
         * @param referenceId 需签名的元素标识
         * @param signNode 生成签名的节点
         */
        public void sign(Document doc, PrivateKey privateKey, String referenceId, Node signNode) {
            if(!isInit){
                throw new RuntimeException("XMLSigner is not init !");
            }
            try{
                //创建 <Reference> 元素,引用指定ID的节点,<Signature> 元素不会被计算在内
                Reference refToRootDoc = signFactory.newReference("#"+referenceId,
                        sha1DigMethod, Collections.singletonList(envelopedTransform), null, null);
                //创建 <SignedInfo> 元素
                SignedInfo signedInfo = signFactory.newSignedInfo(c14nWithCommentMethod,
                        rsa_sha1SigMethod, Collections.singletonList(refToRootDoc));
                //创建签名实例
                XMLSignature signature = signFactory.newXMLSignature(signedInfo, null);
                //创建签名上下文,在指定节点生成
                DOMSignContext dsc = new DOMSignContext(privateKey, signNode);
                //设置签名域命名空间前缀
                if (defaultNamespacePrefix != null) {
                    dsc.setDefaultNamespacePrefix(defaultNamespacePrefix);
                    Element element = (Element)doc.getElementsByTagName(referenceId).item(0);
                    dsc.setIdAttributeNS(element, null, "id");
                }
                //签名
                signature.sign(dsc);
            }catch(Exception e){
                System.out.println(e.getMessage());
                throw new RuntimeException(e);
            }
        }

    再次运行程序后,问题解决。

    具体的加签操作指导,详见如下文档:

    http://www.apihome.cn/api/java/XMLSignatureFactory.html(中文)

    http://docs.oracle.com/javase/6/docs/technotes/guides/security/xmldsig/XMLDigitalSignature.html(英文,但是介绍的更详细,直接拷贝出来即可)

  • 相关阅读:
    每天进步一点点之查找
    每天进步一点点之堆栈思想
    每天进步一点点之大端存储和小端存储
    每天进步一点点之线性表的考察
    每天进步一点点之出栈顺序考点
    React Native 混合开发与实现
    谈谈JavaScript异步代码优化
    谈谈前端异常捕获与上报
    vue插件编写与实战
    vue项目构建与实战
  • 原文地址:https://www.cnblogs.com/wanggangblog/p/4566958.html
Copyright © 2011-2022 走看看