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节点来补充该内容:
解决步骤
- 从X509证书中提取SubjectDN字符串
- 创建
X509Data对象,添加X509SubjectName子节点 - 将自定义的
X509Data添加到签名的KeyInfo中
问题2:修正附件Reference的URI为cid格式
直接使用本地文件路径调用sig.addDocument()会导致URI被设置为文件路径,需要通过cid关联SOAP附件,并自定义解析逻辑:
解决步骤
- 使用
cid:attachmentID作为Reference的URI值 - 自定义
ResourceResolver,根据cid从SOAPMessage中获取附件的输入流 - 注册自定义解析器,替代默认的文件系统解析器
修改后的完整代码
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
相关产品推荐
相关产品推荐

