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

Java中为带附件的SOAP请求添加数字签名的问题求助

带附件SOAP请求数字签名问题解决方案

问题概述

使用xmlsec-3.0.2.jar为带附件的SOAP请求添加数字签名时,出现两个不符合预期的问题:

  • 签名的<ds:KeyInfo>中缺少<ds:X509SubjectName>标签
  • 附件对应的<ds:Reference>的URI属性为本地文件路径,而非要求的cid:attachmentID

问题1:补充ds:X509SubjectName标签

默认调用sig.addKeyInfo(x509Certificate)只会添加<ds:X509Certificate>节点,不会自动生成<ds:X509SubjectName>。需要手动构建X509Data节点来补充该内容:

解决步骤

  1. 从X509证书中提取SubjectDN字符串
  2. 创建X509Data对象,添加X509SubjectName子节点
  3. 将自定义的X509Data添加到签名的KeyInfo中

问题2:修正附件Reference的URI为cid格式

直接使用本地文件路径调用sig.addDocument()会导致URI被设置为文件路径,需要通过cid关联SOAP附件,并自定义解析逻辑:

解决步骤

  1. 使用cid:attachmentID作为Reference的URI值
  2. 自定义ResourceResolver,根据cid从SOAPMessage中获取附件的输入流
  3. 注册自定义解析器,替代默认的文件系统解析器

修改后的完整代码

public void addSignature(Resource keyStoreFile, String keyStorePassword, String keyStoreAlias, SOAPMessage soapMessage) throws Exception {
    // 初始化XML Security
    Init.init();

    // 解析SOAP消息为DOM文档
    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
    dbFactory.setNamespaceAware(true);
    DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    soapMessage.writeTo(baos);
    Document doc = dBuilder.parse(new ByteArrayInputStream(baos.toByteArray()));

    // 创建签名对象
    org.apache.xml.security.signature.XMLSignature sig =
        new org.apache.xml.security.signature.XMLSignature(doc, null, SignatureMethod.RSA_SHA1);
    sig.setId("WmEbXML-Signature-54cl6h00gi08isbf003ient2");

    // 将签名添加到SOAP Header
    Node headerNode = doc.getElementsByTagNameNS("http://schemas.xmlsoap.org/soap/envelope/", "Header").item(0);
    headerNode.appendChild(sig.getElement());

    // 配置SOAP主体的转换规则
    Transforms transforms = new Transforms(doc);
    transforms.addTransform(CanonicalizationMethod.ENVELOPED);
    String xpathExpr = "not(ancestor-or-self::eb:TraceHeaderList or ancestor-or-self::eb:Via)";
    XPathContainer xpathContainer = new XPathContainer(doc);
    xpathContainer.setXPath(xpathExpr);
    transforms.addTransform(Transforms.TRANSFORM_XPATH, xpathContainer.getElement());
    transforms.addTransform(CanonicalizationMethod.INCLUSIVE);

    // 添加SOAP Envelope的签名引用
    sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);

    // 自定义资源解析器:根据cid从SOAPMessage获取附件
    ResourceResolver.register(new ResourceResolverSpi() {
        @Override
        public ResourceResolver newInstance(@SuppressWarnings("rawtypes") Map properties) {
            return new ResourceResolver() {
                @Override
                public XMLInputStream resolveURI(String uri, String baseURI, MessageElement messageElement) throws ResourceResolverException {
                    if (uri.startsWith("cid:")) {
                        String cid = uri.substring(4);
                        try {
                            // 遍历SOAP附件,找到对应cid的附件
                            Iterator<AttachmentPart> attachments = soapMessage.getAttachments();
                            while (attachments.hasNext()) {
                                AttachmentPart part = attachments.next();
                                String contentId = part.getContentId();
                                if (contentId != null && contentId.equals("<" + cid + ">")) {
                                    return new XMLInputStreamImpl(part.getInputStream());
                                }
                            }
                            throw new ResourceResolverException("附件未找到: " + uri, uri, baseURI);
                        } catch (Exception e) {
                            throw new ResourceResolverException(e.getMessage(), uri, baseURI, e);
                        }
                    }
                    return null;
                }

                @Override
                public void setXPathNamespaceContext(XPathNamespaceContext xPathNamespaceContext) {}
            };
        }

        @Override
        public boolean engineCanResolveURI(String uri, String baseURI) {
            return uri.startsWith("cid:");
        }
    }, true);

    // 添加附件的签名引用,使用cid作为URI
    sig.addDocument("cid:attachmentID", null, Constants.ALGO_ID_DIGEST_SHA1);

    // 加载密钥库
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    keyStore.load(keyStoreFile.getInputStream(), keyStorePassword.toCharArray());
    KeyStore.PrivateKeyEntry keyEntry =
        (KeyStore.PrivateKeyEntry) keyStore.getEntry(keyStoreAlias, new KeyStore.PasswordProtection(keyStorePassword.toCharArray()));
    X509Certificate x509Certificate = (X509Certificate) keyEntry.getCertificate();

    // 添加公钥到KeyInfo
    sig.addKeyInfo(x509Certificate.getPublicKey());

    // 手动构建X509Data,包含SubjectName和Certificate
    X509Data x509Data = new X509Data(doc);
    // 添加X509SubjectName
    x509Data.addSubjectName(x509Certificate.getSubjectDN().getName());
    // 添加X509Certificate
    x509Data.addCertificate(x509Certificate);
    // 将自定义X509Data添加到签名的KeyInfo
    sig.getKeyInfo().add(x509Data);

    // 执行签名
    sig.sign(keyEntry.getPrivateKey());

    // 将DOM转换回SOAPMessage
    SOAPPart soapPart = soapMessage.getSOAPPart();
    soapPart.setContent(new DOMSource(doc));
}

内容的提问来源于stack exchange,提问作者NishanM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 15:04:58