CXF客户端+WS安全+MTOM=麻烦?

3
我正在构建一个调用启用WS-Security的Web服务的CXF(版本2.7.4)客户端。它使用BinarySecurityToken、加密和签名。我已经让CXF正常工作在"常规"SOAP调用中,但是当响应以MTOM返回时,客户端会出现奇怪的错误:
org.apache.cxf.binding.soap.SoapFault: 签名或解密无效
    at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.createSoapFault(WSS4JInInterceptor.java:778)
    at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:334)
    at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:96)
...
Caused by: org.apache.xml.security.encryption.XMLEncryptionException: 无法找到URI的解析器cid:urn%3Auuid%3AD62B819A5C8E77D41B1391208838268@apache.org 和基本为空
原始异常是org.apache.xml.security.utils.resolver.ResourceResolverException: 无法找到URI的解析器cid:urn%3Auuid%3AD62B819A5C8E77D41B1391208838268@apache.org 和Base null
    at org.apache.xml.security.encryption.XMLCipherInput.getDecryptBytes(XMLCipherInput.java:134)
    at org.apache.xml.security.encryption.XMLCipherInput.getBytes(XMLCipherInput.java:103)
    ... 46 more
Caused by: org.apache.xml.security.utils.resolver.ResourceResolverException: 无法找到URI的解析器cid:urn%3Auuid%3AD62B819A5C8E77D41B1391208838268@apache.org 和基本为空
    at org.apache.xml.security.utils.resolver.ResourceResolver.internalGetInstance(ResourceResolver.java:130)
    at org.apache.xml.security.utils.resolver.ResourceResolver.getInstance(ResourceResolver.java:87)
    at org.apache.xml.security.encryption.XMLCipherInput.getDecryptBytes(XMLCipherInput.java:130)
    ... 51 more
我尝试在绑定提供程序上调用setMTOMEnabled(true),设置SAAjInInterceptor、AttachmentInInterceptor但是仍然没有成功。有没有想法如何解决它?

我猜另一种选择是编写自定义的ResourceResolver,然后通过该类提供附件。看起来有点过度设计了... - Nick G.
在这种情况下,XML安全的规则是什么?我曾经试着读过它们(几年前),但它们非常令人昏昏欲睡... - Donal Fellows
3
啊,我希望我知道。这是一个非常奇怪的设置。我们需要配置自定义标头,添加二进制安全令牌、用户名令牌和时间戳,然后对所有内容进行签名。响应将被签名和加密。对于大多数调用,CXF 处理得很好,除了带有 MTOM 附件的那个调用...... - Nick G.
1个回答

2
一切都可以追溯到一个事实,即当启用WSS时,CXF与MTOM不兼容 :( CXF邮件列表一 在更多地解决这个问题后,看起来解决的方法之一可能是包括自定义资源解析器和转换。
因此,我添加了一个自定义解析器,以便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消息上的附件实例未被更新。为了修复这个问题并最终产生一个可行的解决方案,我不得不修改DocumentSerializer(通过对内容进行base64编码来解决第1个问题)和XMLCipher(通过替换附件实例来解决第2个问题)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接