I have a service that uses requires SenderVouches authentication, which works fine for CXF-based Java applications. But now we need to integrate an .NET-based application that does not support SenderVouches in it's SOAP/WS-Security Stack.
I looked around a little bit an found this article explaining the underlying theory of the WS-Trust underlying the SenderVouches: https://www.xml.com/pub/a/ws/2003/06/24/ws-trust.html
In there I found this diagram which could be a solution to the problem we're facing, using a SOAP "Gateway". Does Apache CXF has support for implementing the Gateway part?
I searched around, but most results are unrelated to implementing a SOAP Gateway.
I ended up using a combination of Membrane API Gateway and CXF:
package com.example.wsproxy.membrane;
import com.example.wsproxy.configmodel.ServiceProperties;
import com.predic8.membrane.core.exchange.Exchange;
import com.predic8.membrane.core.http.Response;
import com.predic8.membrane.core.interceptor.AbstractInterceptor;
import com.predic8.membrane.core.interceptor.Outcome;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.cxf.jaxws.DispatchImpl;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
@Slf4j
public class MembraneCxfInterceptor extends AbstractInterceptor {
protected final Dispatch<StreamSource> dispatch;
protected final MessageFactory messageFactory;
public MembraneCxfInterceptor(ServiceProperties serviceProperties) throws SOAPException {
dispatch = setupDispatch(serviceProperties);
messageFactory = MessageFactory.newInstance();
}
private Dispatch<StreamSource> setupDispatch(ServiceProperties serviceProperties){
log.debug("Setting up dispatcher for {}", serviceProperties.getServiceName());
Service service = Service.create(serviceProperties.getWsdlLocation(), serviceProperties.getServiceName());
Dispatch<StreamSource> d = service.createDispatch(service.getPorts().next(), StreamSource.class, Service.Mode.MESSAGE);
log.debug("Warming up CXF...");
// warming up CXF
((DispatchImpl<StreamSource>) d).getClient().getConduitSelector().getEndpoint();
return d;
}
@Override
public Outcome handleRequest(Exchange exc) throws Exception {
StreamSource request = new StreamSource(exc.getRequest().getBodyAsStream());
StreamSource response = dispatch.invoke(request);
@SuppressWarnings("squid:S2095") // closed by membrane
PipedInputStream pis = new PipedInputStream();
@SuppressWarnings("squid:S2095") // closed in finally block
PipedOutputStream pos = new PipedOutputStream(pis);
new Thread(() -> {
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
// TODO: Faults?
t.transform(response, new StreamResult(pos));
} catch (TransformerException e) {
throw new CxfInterceptorTransformerException(e);
} finally {
try {
pos.close();
} catch (IOException e) {
log.error("error closing piped output stream!", e);
}
}
}).start();
exc.setResponse(Response.ok().body(pis, true).build());
return Outcome.RETURN;
}
static class CxfInterceptorTransformerException extends RuntimeException {
public CxfInterceptorTransformerException(TransformerException e){
super(e);
}
}
}
package com.example.wsproxy.configmodel;
import lombok.Data;
import javax.xml.namespace.QName;
import java.net.URL;
@Data
public class ServiceProperties {
private URL wsdlLocation;
private QName serviceName;
}