Java环境下调用带WS-Security的SOAP WebService签名问题咨询
实现WS-Security签名的分步解决方案(基于JAXB)
当然可以搞定!WS-Security的签名确实是SOAP服务调用里容易卡壳的环节,尤其是你已经用JAXB搞定了信封生成的情况下,咱们一步步来拆解,帮你把签名这块打通:
第一步:引入必要的依赖
XML数字签名的逻辑手写太容易出错,咱们直接用成熟的Apache Santuario库来处理。如果用Maven,添加以下依赖:
<dependency> <groupId>org.apache.santuario</groupId> <artifactId>xmlsec</artifactId> <version>2.2.3</version> <!-- 建议用最新稳定版 --> </dependency>
第二步:准备密钥与证书
签名需要私钥(你用来签名),服务端验证需要对应的公钥证书。如果是测试阶段,可以用keytool生成密钥库:
keytool -genkeypair -alias my-service-key -keyalg RSA -keystore my-keystore.jks -storepass my-store-pass -keypass my-key-pass
然后在代码中加载私钥和证书:
// 加载密钥库 KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream("my-keystore.jks"), "my-store-pass".toCharArray()); // 获取私钥和证书 PrivateKey privateKey = (PrivateKey) keyStore.getKey("my-service-key", "my-key-pass".toCharArray()); X509Certificate cert = (X509Certificate) keyStore.getCertificate("my-service-key");
第三步:给JAXB生成的SOAP信封添加签名
首先把JAXB生成的SOAP对象转换成DOM文档(因为XML签名需要操作DOM节点):
JAXBContext jaxbContext = JAXBContext.newInstance(YourSoapEnvelopeClass.class); Marshaller marshaller = jaxbContext.createMarshaller(); Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); marshaller.marshal(yourGeneratedSoapEnvelope, doc);
接下来创建WS-Security的签名,并把它插入到SOAP Header的wsse:Security节点中:
// 初始化XML签名工厂 XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM"); // 配置签名的引用(对整个SOAP信封进行签名,用ENVELOPED变换) Reference signatureRef = sigFactory.newReference( "", sigFactory.newDigestMethod(DigestMethod.SHA256, null), Collections.singletonList(sigFactory.newTransform(Transform.ENVELOPED, (XMLStructure) null)), null, null ); // 创建签名信息(指定规范化方法和签名算法) SignedInfo signedInfo = sigFactory.newSignedInfo( sigFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS, (XMLStructure) null), sigFactory.newSignatureMethod(SignatureMethod.RSA_SHA256, null), Collections.singletonList(signatureRef) ); // 准备密钥信息(把证书包含在签名里,方便服务端验证) KeyInfoFactory keyInfoFactory = sigFactory.getKeyInfoFactory(); X509Data x509Data = keyInfoFactory.newX509Data(Collections.singletonList(cert)); KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data)); // 创建签名对象 XMLSignature signature = sigFactory.newXMLSignature(signedInfo, keyInfo); // 在SOAP Header中添加wsse:Security节点 SOAPEnvelope envelope = ((SOAPDocument) doc).getSOAPPart().getEnvelope(); SOAPHeader header = envelope.getHeader(); if (header == null) { header = envelope.addHeader(); } Element securityElement = doc.createElementNS( "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "wsse:Security" ); header.appendChild(securityElement); // 执行签名,把签名结果写入securityElement节点 DOMSignContext signContext = new DOMSignContext(privateKey, securityElement); signature.sign(signContext);
第四步:发送签名后的SOAP请求
把签名后的DOM文档转换成字节流,通过HTTP发送给服务端:
// 把DOM文档转换成字节流 Transformer transformer = TransformerFactory.newInstance().newTransformer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); transformer.transform(new DOMSource(doc), new StreamResult(baos)); byte[] signedSoapBytes = baos.toByteArray(); // 发送请求 URL serviceUrl = new URL("你的WebService地址"); HttpURLConnection conn = (HttpURLConnection) serviceUrl.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "text/xml;charset=UTF-8"); conn.setDoOutput(true); try (OutputStream os = conn.getOutputStream()) { os.write(signedSoapBytes); os.flush(); } // 处理服务端响应... int responseCode = conn.getResponseCode(); // 读取响应内容...
常见踩坑点排查
- 命名空间错误:
wsse:Security的命名空间必须和服务端要求完全一致,这是最容易导致签名验证失败的原因之一。 - 算法不匹配:确认服务端要求的摘要算法(比如SHA256)和签名算法(比如RSA_SHA256),不要用过时的SHA1。
- 签名位置错误:WS-Security签名必须放在SOAP Header的
wsse:Security节点内,不能随意插入其他位置。 - 密钥加载失败:检查密钥库路径、密码、别名是否正确,确保私钥能正常从密钥库中取出。
内容的提问来源于stack exchange,提问作者Otávio
相关产品推荐
相关产品推荐

