这一切都可以追溯到启用 WSS 时 CXF 无法与 MTOM 一起使用的事实 :(
CXF Mailing List One
在解决了这个问题之后,它看起来像是一种解决方法,可能包括自定义资源解析器和转换。
所以我添加了一个自定义解析器,以便 WSS 知道在哪里可以找到附件内容:
package org.integration.client;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collection;
import org.apache.cxf.message.Attachment;
import org.apache.log4j.Logger;
import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xml.security.utils.resolver.ResourceResolverContext;
import org.apache.xml.security.utils.resolver.ResourceResolverException;
import org.apache.xml.security.utils.resolver.ResourceResolverSpi;
public class AttachmentResolverSpi extends ResourceResolverSpi {
private static final String SUPPORTED_URI_PREFIX = "cid:urn";
private static final String ATTACHMENT_PREFIX = "cid:";
private static Logger logger = Logger.getLogger(AttachmentResolverSpi.class);
private Collection<Attachment> attachments;
@Override
public boolean engineIsThreadSafe() {
return false;
}
@Override
public boolean engineCanResolveURI(ResourceResolverContext context) {
if (context.uriToResolve == null) {
return false;
}
return context.uriToResolve.startsWith(SUPPORTED_URI_PREFIX);
}
@Override
public XMLSignatureInput engineResolveURI(ResourceResolverContext context)
throws ResourceResolverException {
String resourceId = getResourceId(context);
if (logger.isInfoEnabled()) {
logger.info("Looking up: " + resourceId);
}
Attachment attachment = getAttachment(resourceId);
if (attachment == null) {
logger.error("Unable to resolve attachment for " + resourceId);
throw new ResourceResolverException(context.uriToResolve, context.attr, context.baseUri);
}
if (logger.isInfoEnabled()) {
logger.info("Found attachment: " + attachment);
}
XMLSignatureInput result;
try {
result = new XMLSignatureInput(attachment.getDataHandler().getInputStream());
} catch (IOException e) {
logger.error("Unable to create xml signature input", e);
throw new ResourceResolverException(context.uriToResolve, context.attr, context.baseUri);
}
return result;
}
private String getResourceId(ResourceResolverContext context) {
String resourceId = context.uriToResolve;
try {
resourceId = URLDecoder.decode(resourceId, "UTF-8");
} catch (UnsupportedEncodingException e1) {
throw new RuntimeException("Unable to decode", e1);
}
if (resourceId.startsWith(ATTACHMENT_PREFIX)) {
resourceId = resourceId.substring(ATTACHMENT_PREFIX.length());
}
return resourceId;
}
private Attachment getAttachment(String resourceId) {
Collection<Attachment> attachments = getAttachments();
if (attachments == null) {
return null;
}
for(Attachment a : attachments) {
if (logger.isDebugEnabled()) {
logger.debug("Comparing " + a.getId() + " with " + resourceId);
}
if (a.getId().equals(resourceId)) {
return a;
}
}
return null;
}
protected Collection<Attachment> getAttachments() {
if (attachments == null) {
attachments = AttachmentCachingSaajInInterceptor.getAttachments();
}
return attachments;
}
}
现在,额外的调整是告诉 CXF 如何获取此内容的签名值:
package org.integration.client;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.apache.xml.security.exceptions.Base64DecodingException;
import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xml.security.transforms.Transform;
import org.apache.xml.security.transforms.TransformSpi;
import org.apache.xml.security.transforms.TransformationException;
import org.xml.sax.SAXException;
public class TransformAttachmentCiphertext extends TransformSpi {
public static final String TRANSFORM_ATTACHMENT_CIPHERTEXT =
"http://docs.oasis-open.org/wss/oasis-wss-SwAProfile-1.1#Attachment-Ciphertext-Transform";
/**
* @see org.apache.xml.security.transforms.TransformSpi#engineGetURI()
*/
@Override
public String engineGetURI() {
return TRANSFORM_ATTACHMENT_CIPHERTEXT;
}
/**
* @see org.apache.xml.security.transforms.TransformSpi#enginePerformTransform(org.apache.xml.security.signature.XMLSignatureInput,
* java.io.OutputStream, org.apache.xml.security.transforms.Transform)
*/
@Override
protected XMLSignatureInput enginePerformTransform(XMLSignatureInput input,
OutputStream os, Transform transformObject) throws IOException,
CanonicalizationException, InvalidCanonicalizerException,
TransformationException, ParserConfigurationException, SAXException {
if (input.isOctetStream() || input.isNodeSet()) {
if (os == null) {
byte[] contentBytes = input.getBytes();
XMLSignatureInput output = new XMLSignatureInput(contentBytes);
return output;
}
if (input.isByteArray() || input.isNodeSet()) {
os.write(input.getBytes());
} else {
try {
org.apache.xml.security.utils.Base64.decode(new BufferedInputStream(input.getOctetStreamReal()), os);
} catch (Base64DecodingException e) {
throw new IOException("Unable to decode real octet stream", e);
}
}
XMLSignatureInput output = new XMLSignatureInput(new byte[] {});
output.setOutputStream(os);
return output;
}
return input;
}
}
最后,这两个元素需要在 CXF 中注册:
import org.apache.xml.security.utils.resolver.ResourceResolver;
...
ResourceResolver.register(AttachmentResolverSpi.class, true);
org.apache.xml.security.transforms.Transform.register(TransformAttachmentCiphertext.TRANSFORM_ATTACHMENT_CIPHERTEXT, TransformAttachmentCiphertext.class);
请不要问我是怎么做的,但是在这些操作之后,CXF 开始解密带有附件的 SOAP 消息。但这不是一个完整的解决方案,因为它仍然不适用于 MTOM 附件...发生的情况是 CXF 尝试使用附件内容更新 DOM 模型。这种方法不起作用有两个原因。首先,二进制附件通常不会生成格式良好的 XML 元素。其次,SOAP 消息上的 Attachment 实例没有更新。为了解决这个问题,并最终产生一个可行的解决方案,我不得不破解 DocumentSerializer(如果内容无法解析,则通过 base64 编码解决问题 1)和 XMLCipher(通过替换附件实例解决问题 2)。