WS Security中XML签名STR-Transform算法原理及签名实现问询
我来帮你拆解STR-Transform的工作原理,以及针对你这个X509 Thumbprint场景的具体实现步骤——我之前处理过类似的WS-Security签名需求,踩过不少坑,应该能帮到你。
一、STR-Transform 核心工作原理
STR-Transform(全称Security Token Reference Transform)是OASIS WS-Security规范专门为SecurityTokenReference(STR)节点设计的转换算法。
在WS-Security场景中,STR用来引用签名用的安全令牌(比如你的X509证书),但不同系统生成的STR可能有细微格式差异:比如命名空间前缀不同、多了无关空白、带了非必要属性。这些小差异会导致签名时计算的摘要不一致,最终验证失败。
STR-Transform的核心作用就是:
- 提取STR中与令牌身份绑定的核心信息,生成一个标准化的STR片段
- 对这个标准化片段应用指定的XML规范化算法(比如你用的
exc-c14n#),确保签名和验证时使用完全一致的输入,彻底解决格式差异问题
二、针对X509 Thumbprint STR的签名分步算法
结合你提供的签名结构(用X509证书Thumbprint引用,需要对STR本身签名),具体步骤如下:
步骤1:定位目标STR节点
首先找到消息中带有wsu:Id="str_U1sjQ5j8JtKnObLk"的<wsse:SecurityTokenReference>节点——这就是你要签名的核心对象:
<wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="str_U1sjQ5j8JtKnObLk"> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1">h5...ow=</wsse:KeyIdentifier> </wsse:SecurityTokenReference>
步骤2:执行STR-Transform生成标准化STR片段
根据WS-Security规范,针对X509 Thumbprint类型的STR,STR-Transform会生成一个仅保留核心标识信息的新STR元素:
- 保留原STR的
wsse11:TokenType属性(用来明确令牌是X509v3类型) - 完整保留
<wsse:KeyIdentifier>子元素(包含EncodingType、ValueType和Thumbprint值,这是绑定证书的核心) - 移除所有非必要属性(比如
wsu:Id,因为签名引用已经通过URI关联,转换后的片段不需要这个ID) - 确保命名空间声明完整且符合规范
转换后的中间STR片段大致如下:
<wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"> <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1">h5...ow=</wsse:KeyIdentifier> </wsse:SecurityTokenReference>
步骤3:应用指定的规范化算法
对上述标准化STR片段,应用你指定的http://www.w3.org/2001/10/xml-exc-c14n#(Exclusive XML Canonicalization)算法。这个步骤会统一处理XML的空白、命名空间前缀、属性顺序等,确保跨系统的一致性——这一步是避免验证失败的关键。
步骤4:计算摘要并生成签名引用
对规范化后的字节流,使用http://www.w3.org/2001/04/xmlenc#sha256摘要算法计算出DigestValue,然后把这个值填入<dsig:Reference>节点中,就得到了你提供的签名结构里的STR引用部分。
步骤5:完成整体签名
把所有Reference(包括Timestamp和STR的)的DigestValue整合到<dsig:SignedInfo>中,使用http://www.w3.org/2000/09/xmldsig#rsa-sha1算法对整个SignedInfo进行签名,生成<dsig:SignatureValue>。
三、Java实现示例(使用Apache Santuario)
Apache Santuario是Java生态中处理XML签名的成熟库,以下是针对你场景的简化实现:
import org.apache.xml.security.Init; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.transforms.Transforms; import org.apache.xml.security.utils.Constants; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.xml.parsers.DocumentBuilderFactory; import java.io.FileInputStream; import java.security.PrivateKey; import java.security.cert.X509Certificate; public class STRTransformSignature { public static void main(String[] args) throws Exception { // 初始化XML Security库(必须先执行) Init.init(); // 加载你的SOAP消息文档 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); // 必须开启命名空间支持 Document soapDoc = dbf.newDocumentBuilder().parse(new FileInputStream("your-soap-message.xml")); // 定位到要签名的STR节点(通过wsu:Id) Element strNode = (Element) soapDoc.getElementById("str_U1sjQ5j8JtKnObLk"); // 创建XMLSignature实例,指定签名算法和默认规范化方法 XMLSignature signature = new XMLSignature(soapDoc, "", XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1); signature.getSignedInfo().setCanonicalizationMethod(Constants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); // 配置STR-Transform Transforms transforms = new Transforms(soapDoc); // 添加STR-Transform算法 Element strTransform = transforms.addTransform(Constants.TRANSFORM_STR_TRANSFORM); // 设置TransformationParameters:指定规范化算法 Element params = soapDoc.createElementNS(Constants.NS_WSSE, "wsse:TransformationParameters"); Element c14nMethod = soapDoc.createElementNS(Constants.SignatureSpecNS, "dsig:CanonicalizationMethod"); c14nMethod.setAttribute("Algorithm", Constants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); params.appendChild(c14nMethod); strTransform.appendChild(params); // 添加STR的Reference,指定摘要算法 signature.addReference("#str_U1sjQ5j8JtKnObLk", transforms, Constants.ALGO_ID_DIGEST_SHA256); // 添加其他Reference(比如Timestamp,这里省略,你可以参考现有逻辑添加) // signature.addReference("#Timestamp_C1Ih1AB1vpPT5uG2", ...); // 将Signature节点添加到SOAP的Security头中(假设Security头已存在) Element securityHeader = (Element) soapDoc.getElementsByTagNameNS(Constants.NS_WSSE, "Security").item(0); securityHeader.appendChild(signature.getElement()); // 加载你的X509私钥和证书(这里需要你自己实现加载逻辑) PrivateKey privateKey = loadYourPrivateKey(); X509Certificate cert = loadYourX509Cert(); // 执行签名 signature.sign(privateKey); // 输出签名后的SOAP文档(这里可以添加代码将Document转为XML字符串) printDocument(soapDoc); } // 以下是占位方法,需要你自己实现 private static PrivateKey loadYourPrivateKey() { /* 加载私钥逻辑 */ return null; } private static X509Certificate loadYourX509Cert() { /* 加载证书逻辑 */ return null; } private static void printDocument(Document doc) { /* 输出XML文档逻辑 */ } }
四、关键注意事项
- 命名空间必须严格一致:
wsse、wsse11、wsu、dsig的命名空间URI必须和规范完全匹配,不能有拼写错误 - TransformationParameters不能少:STR-Transform必须指定规范化算法参数,否则签名和验证会直接失败
- STR的wsu:Id必须唯一:确保每个STR的ID在整个SOAP消息中是唯一的,Reference的URI要准确指向这个ID
内容的提问来源于stack exchange,提问作者Guillermo Gutiérrez

