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(英文,但是介紹的更詳細,直接拷貝出來即可)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM