我们有一个获取下一条 SOAP 消息的 Web 服务。我只发布标题,这是我们问题的重要组成部分。
<SOAP-ENV:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" SOAP-ENV:mustUnderstand="1">
<wsse:BinarySecurityToken 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/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="X509-65E18DC0CA7D9A38B214168992655731">THE_CERTIFICATE</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="SIG-65E18DC0CA7D9A38B214168992656685">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="SOAP-ENV"/>
</ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#id-65E18DC0CA7D9A38B214168992656044">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList=""/>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>DIGEST_VALUE</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>THE_SIGNATURE</ds:SignatureValue>
<ds:KeyInfo Id="KI-65E18DC0CA7D9A38B214168992655892">
<wsse:SecurityTokenReference wsu:Id="STR-65E18DC0CA7D9A38B214168992655893">
<wsse:Reference URI="#X509-65E18DC0CA7D9A38B214168992655731" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
用于签署请愿书的证书由 Spring 使用下一个配置(服务器端)进行验证:
<sws:interceptors>
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Signature" />
<property name="validationSignatureCrypto">
<bean
class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="passtrustore" />
<property name="keyStoreLocation" value="classpath:/ts-webservice.jks" />
</bean>
</property>
<property name="securementActions" value="Signature" />
<property name="securementUsername" value="user" />
<property name="securementPassword" value="pass" />
<property name="securementSignatureKeyIdentifier" value="DirectReference" />
<property name="securementSignatureCrypto">
<bean
class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="passkeystore" />
<property name="keyStoreLocation" value="classpath:/ks-webservice.jks" />
</bean>
</property>
</bean>
</sws:interceptors>
一切正常,但我们尝试在端点中提取二进制安全令牌,因为我们使用该证书获取签名者的 ID 并在响应中返回一些个人信息。我们可以再次将其添加为此方法的输入参数,但如果标头中已经有相同的证书,则我们不想发送两次相同的证书。
返回请求的方法是下一个 onee:
@PayloadRoot(localPart = "ValidateUserRequest", namespace = GET_TARGET_NAMESPACE)
public @ResponsePayload
ValidateUserResponse validateUser(@RequestPayload ValidateUserRequest request, MessageContext messageContext) throws WSSecurityException,
CertificateException {
// read SOAP Header
SoapMessage mc = (SoapMessage) messageContext.getRequest();
String soapNamespace = WSSecurityUtil.getSOAPNamespace(mc.getDocument().getDocumentElement());
Element elem = WSSecurityUtil.getDirectChildElement(mc.getDocument().getDocumentElement(), WSConstants.ELEM_HEADER, soapNamespace);
// get the BinarySignature tag
// FIRST getFirstChild() is NULL if we have validated the request
Node binarySignatureTag = elem.getFirstChild().getFirstChild();
BinarySecurity token = new X509Security((Element) binarySignatureTag);
InputStream in = new ByteArrayInputStream(token.getToken());
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
// do stuff with the certificate and return values
}
当 Spring 验证请求的签名时,似乎删除了标头,因此我们无法访问标头元素的第一个子元素。如果我们在应用程序上下文中注释验证部分,则前面的代码就像一个魅力,我们得到证书。
我们如何避免这种行为?为什么验证后会删除请求中的标头?
谢谢!
经过漫长的夜晚研究和阅读 Spring 文档,我找到了解决方法。我不明白为什么 Spring 会使用标头,或者为什么我的端点没有收到标头,但是我们可以使用下一个代码提取证书(激活签名验证):
@PayloadRoot(localPart = "ValidateUserRequest", namespace = GET_TARGET_NAMESPACE)
public @ResponsePayload ValidateUserResponse validateUser(@RequestPayload ValidateUserRequest request, MessageContext messageContext) throws WSSecurityException, CertificateException {
List<WSHandlerResult> handlerResults = (List<WSHandlerResult>) messageContext.getProperty(WSHandlerConstants.RECV_RESULTS);
WSHandlerResult rResult = handlerResults.get(0);
List<WSSecurityEngineResult> results = rResult.getResults();
WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.SIGN);
X509Certificate returnCert = null;
if (actionResult != null) {
returnCert = (X509Certificate) actionResult.get(WSSecurityEngineResult.TAG_X509_CERTIFICATE);
}
// do stuff with the certificate and return values
}
经过一些研究,我发现Spring正在将验证中处理的结果保存在MessageContext中,其中包含证书(见Wss4jSecurityInterceptor#updateContextWithResults
)。