How do I test a Spring-WS webservice that Requires soapHeaders? - java

I have a spring-ws (2.0.2) service I have implemented that requires some custom elements in the soap header. I am trying to use Spring's MockWebServiceClient to generate a valid request to test the dispatcher, marshallers, etc.
The problem I am getting is that the MockWebSerivce only seems to support the Soap Body (the payload).
How can I access the soap request being generated to get the right headers into it?
If there is a better library for doing this other than Spring's MockWebServiceClient, that would be fine too.
Related Links:
http://forum.springsource.org/showthread.php?101708-MockWebServiceClient-amp-WS-Security
Add SoapHeader to org.springframework.ws.WebServiceMessage

I had the similar problem when I wanted to test spring web service with Security, I ended up using the Spring Interceptors to modify the header before they reach end point, I enabled the interceptors only for testing.
Create an interceptor, I implemented the SmartEndpointInterceptor, You can use the other interceptors if you choose
public class ModifySoapHeaderInterceptor implements
SmartEndpointInterceptor
{
//WSConstants.WSSE_NS;
private static final String DEFAULT_SECURITY_URL = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
private static final String SECURITY_TAG = "Security";
private static final String SECURITY_PREFIX = "wsse";
private static final String USER_NAME_TOKEN = "UsernameToken";
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint)
throws Exception
{
SaajSoapMessage saajSoapMessage(SaajSoapMessage)messageContext.getRequest());
SOAPHeader soapHeader = saajSoapMessage.getSaajMessage().getSOAPPart()
.getEnvelope().getHeader();
//you can modify header's as you choose
Name headerElement = saajSoapMessage.getSaajMessage().getSOAPPart()
.getEnvelope()
.createName(SECURITY_TAG, SECURITY_PREFIX, DEFAULT_SECURITY_URL);
SOAPHeaderElement soapHeaderElement = soapHeader
.addHeaderElement(headerElement);
SOAPElement usernameToken = soapHeaderElement.addChildElement(
USER_NAME_TOKEN, SECURITY_PREFIX);
SOAPElement userNameElement = usernameToken.addChildElement("Username",
SECURITY_PREFIX);
userNameElement.addTextNode("userid");//you can inject via spring
SOAPElement passwordElement = usernameToken.addChildElement("Password",
SECURITY_PREFIX);
passwordElement.addTextNode("password");
return true;
}
}
Configure this interceptor in spring context
<sws:interceptors>
<bean class="prasanna.ws.security.wss4j.ModifySoapHeaderInterceptor"/>
</sws:interceptors>
This will add the necessary security headers to the message before it reaches the end point, You can still use MockWebServiceClient to test your web service.

As you noted, the MockWebServiceClient sendRequest() method only sets up the SOAP body with the payload given it. It does not touch the SOAP header.
To set up the SOAP header as well you can create a class that implements the RequestCreator interface and sets the SOAP header. Pass an instance of this class to the sendRequest() method.
For example:
class SoapActionCreator implements RequestCreator {
private final Source payload;
public SoapActionCreator(Source payload) {
this.payload = payload;
}
#Override
public WebServiceMessage createRequest(WebServiceMessageFactory webServiceMessageFactory)
throws IOException {
WebServiceMessage webServiceMessage =
new PayloadMessageCreator(payload).createMessage(webServiceMessageFactory);
SoapMessage soapMessage = (SoapMessage) webServiceMessage;
SoapHeader header = soapMessage.getSoapHeader();
// Add an Action element to the SOAP header
StringSource headerSource = new StringSource(
"<wsa:Action xmlns:wsa=\"http://www.w3.org/2005/08/addressing\">https://example.com/foo/bar</wsa:Action>");
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(headerSource, header.getResult());
return webServiceMessage;
}
}
Then use SoapActionCreator like this:
SoapActionCreator soapActionCreator = new SoapActionCreator(requestPayload);
mockClient.sendRequest(soapActionCreator).
andExpect(payload(responsePayload));
where requestPayload is the SOAP request body and responsePayload is the entire SOAP response (header and body).

I did find the smock library which does what I really wanted: just takes a text file with the whole request in it.
http://code.google.com/p/smock/wiki/SpringWs
It supports the same request and response matchers as the spring provided stuff. It also keeps my tests very self contained. (Rather than a whole new class that would only be used in my test cases.)

Related

Set Header of A Soap Request in Spring

I have been working on integrating a SOAP API with my Spring API. I have to add headers to my request,but i'm getting this is,
Could not complete request
org.springframework.ws.soap.client.SoapFaultClientException: No WS-Security header found
Please find my code below
public String createSaleOrder(Suborder suborder)
{
SaleOrder saleorder = new SaleOrder();
saleorder = setSaleOrderObject(suborder);
CreateSaleOrderRequest request = new CreateSaleOrderRequest();
request.setSaleOrder(saleorder);
this.getWebServiceTemplate().marshalSendAndReceive(uri, request,
new WebServiceMessageCallback() {
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException
{
SoapMessage soapmessage = (SoapMessage)message;
SoapHeader header = soapmessage.getSoapHeader();
StringBuilder soapheader = new StringBuilder();
soapheader.append("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ser=\"http://wewewqdad.com/services/\">");
soapheader.append("<soapenv:Header>");
soapheader.append("<wsse:Security soapenv:mustUnderstand=\"1\" xmlns:wsse=\"http://docs.oasis-open.asdasda-1.0.xsd\"> ");
soapheader.append("<wsse:UsernameToken wsu:Id=\"UsernameToken-2\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oaasdasd-dasdasd-1.0.xsd\">");
soapheader.append("<wsse:Username>username</wsse:Username>");
soapheader.append("<wsse:Password Type=\"http://docs.aseasdasd-wss-username-token-profile-1.0#PasswordText\">password</wsse:Password>");
soapheader.append("<wsse:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">VMEZT//J0bZw7HfZZyXAZQ==</wsse:Nonce>");
soapheader.append("<wsu:Created>2014-09-04 T1015.41.649Z</wsu:Created>");
soapheader.append("</wsse:UsernameToken>");
soapheader.append("</wsse:Security>");
soapheader.append("</soapenv:Header>");
soapheader.append("<soapenv:Body>");
soapheader.append("</soapenv:Body>");
soapheader.append("</soapenv:Envelope>");
StringSource HeaderSource = new StringSource(soapheader.toString());
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(HeaderSource,header.getResult());
}
});
return "Pushed";
}
UPDATE
The header is set. But the request object now contains extra tags which is not required and hence throwing errors. And also the tags like how can we change these to
Raw soaprequest is given below :
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header><soapenv:Envelope xmlns:ser="http://abc.efg.com/services/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/asdasdasd-1.0.xsd" soapenv:mustUnderstand="1"> <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-2"><wsse:Username>asdasdasd</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oaasdasda-1.0#PasswordText">adsasdasdasda</wsse:Password><wsse:Nonce EncodingType="http://dasdasda004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">VMESASDASZT//asdasdasd</wsse:Nonce><wsu:Created>2014-09-04 T1015.41.649Z</wsu:Created></wsse:UsernameToken></wsse:Security></soapenv:Header></soapenv:Envelope></SOAP-ENV:Header><SOAP-ENV:Body><ns2:CreateSaleOrderRequest xmlns:ns2="http://asdasd.com/services/"><ns2:SaleOrder><ns2:DisplayOrderCode>156</ns2:DisplayOrderCode></ns2:SaleOrder></ns2:CreateSaleOrderRequest></SOAP-ENV:Body></SOAP-ENV:Envelope>
In your soapHeader you wrote more than the actual header but the whole Soap Envelope. Not sure this is expected.
Anyway, you could use a library to handle WS Security like WSS4J (often included in other frameworks).

SOAP Service Client doesn't send Mime headers for Basic Auth

Some background: This is a Weblogic Web Services created Service client creates via Eclipse. I believe this uses clientgen behind the scenes.
I'm trying to make a SOAP call that requires preemptive Basic Authentication. The request is being sent but the Mimeheaders I'm setting are not going with it. The recipient of the call has informed me that the request itself is coming through but any mimeheaders I set are not.
The service call is rather simple.
DescriptionService service = new DescriptionService(wsdlLocation, new QName("urn:descriptionService.service.company.com", "DescriptionService"));
service.setHandlerResolver(new HandlerResolver() {
#SuppressWarnings("rawtypes")
#Override
public List<Handler> getHandlerChain(final PortInfo portInfo) {
final List<Handler> handlerList = new ArrayList<Handler>();
handlerList.add(new SOAPDescriptionServiceHeaderHandler());
return handlerList;
}
});
DescriptionServicePortType portType = service.getDescriptionServicePort();
DescriptionRequest request = new DescriptionRequest();
request.setId(id);
DescriptionResponse description = portType.describe(request);
The handler is where I set the Mimeheaders:
#Override
public boolean handleMessage(final SOAPMessageContext context) {
final Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
final SOAPMessage message = context.getMessage();
if (outboundProperty.booleanValue()) {
try {
MimeHeaders mimeheaders = message.getMimeHeaders();
String encodedAuth = Base64.encode(new String("un:pw").getBytes());
mimeheaders.addHeader("Authorization", "Basic " + encodedAuth);
this.logMessage(message, outboundProperty.booleanValue(), false);
} catch (final Exception e) {
// Log Error
}
} else {
this.logMessage(message, outboundProperty.booleanValue(), false);
}
return true;
}
It does hit this handler and set the mimeheaders. If I set a break point and look at the mime headers before it leaves the handleMessage method, I can see that they are set.
I'm able to call the request and get a response in SoapUI. I set up preemptive basic auth and it works fine. When I send the request through the Java Client, I get no response and actually get an error that says it's the incorrect content type. I believe this error is referring to the fault response as I don't actually get the response (doesn't hit the handleMessage() method in the handler either) and I know the request is going through with text/xml which is what the error is asking for.
I'm unsure if it has something to do with the "preemptive" requirement? Is there a way to set basic auth set up this way as preemptive?
Thoughts?
Any assistance would be appreciated.
Basic Auth is done at the HTTP layer, not the SOAP layer, so you need to configure the underlying HTTP library. (MIME headers have nothing to do with it)
For example for CXF, have a look at this question HTTP basic authentication through CXF interceptor not working

Deserialize header in jaxws handler

I am using a jax-ws Handler to intercept a SOAP message so that I can grab certain elements from the Header. Is there a way to deserialize a Header and parse it into either wsdl2java or xmlbeans generated object? I am currently migrating from AXIS to CXF. Under AXIS, I could get the Header xml and parse it using a Factory class. I have not found a good way to do this with the objects generated from CXF wsdl2java. I realize that I can use the getElementsByTagName to retrieve each Node, but it is just easier to work with the actual object.
You can use SAAJ API to manipulate the SOAP message directly. Extend AbstractSoapInterceptor by implementing the handleMessage() method and add it to the chain of InInterceptors.
public class MyCustomInInterceptor extends AbstractSoapInterceptor {
public void handleMessage(SoapMessage soapMessage) throws Fault {
try {
SOAPMessage message = soapMessage.getContent(SOAPMessage.class);
SOAPPart sp = message.getSOAPPart();
SOAPEnvelope se = sp.getEnvelope();
SOAPBody sb = se.getBody();
SOAPHeader sh = se.getHeader();
}
catch(Exception e)
{
log.error(e);
throw new Fault(e);
}
}
}
NOTE: You will also need to attach SAAJInInterceptor to the chain of interceptors or else soapMessage.getContent(SOAPMessage.class) will return null

How can a Spring Soap interceptor modify the contents of a message?

I'm trying to write an interceptor for a web service that will modify the contents of the Soap message before is sent on to the endpoint. If a client sent a message where the value of some element is 1, I want to be able to alter that element to a 2 so that, when the message arrives at the endpoint, it looks as if the client submitted a 2 instead of a 1. I'm not sure if this is a difficult task which is elluding me, or an easy task which I am making harder than it needs to be.
I have stepped through some of the Spring interceptors; but the validation and logging interceptors don't every alter the message that is in transit. The Wss4jSecurityInterceptor does add some properties to the MessageContext; but I haven't been able to leverage anything that it is doing. I have a shell of an interceptor; but nothing that is doing anything of any value.
public boolean handleRequest(MessageContext messageContext, Object endpoint)
throws Exception {
SaajSoapMessage saajSoapMessage = (SaajSoapMessage) messageContext
.getRequest();
SOAPMessage soapMessage = saajSoapMessage.getSaajMessage();
SOAPBody soapBody = soapMessage.getSOAPBody();
return true;
}
I was hoping there was a chance that soembody else had already solved this particular problem. Any insight would be appreciated. Thanks.
Modifying the payload is a little bit tricky. The only way I've found to make this work is to use the getPayloadSource() and getPayloadResult() methods on SoapBody, which expose javax.xml.transform-friendly objects for manipulating the data.
It's annoyingly heavyweight, but you can do something like this:
Transformer identityTransform = TransformerFactory.newInstance().newTransformer();
DOMResult domResult = new DOMResult();
identityTransform.transform(soapBody.getPayloadSource(), domResult);
Node bodyContent = domResult.getNode(); // modify this
identityTransform.transform(new DOMSource(bodyContent), soapBody.getPayloadResult());
I'd love to see a better way of doing this.
I modified the code in this answer to insert an <authentication/> element into all SOAP body requests:
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
logger.trace("Enter handleMessage");
try {
SaajSoapMessage request = (SaajSoapMessage) messageContext.getRequest();
addAuthn(request);
} catch (Exception e) {
logger.error(e.getMessage(),e);
}
return true;
}
protected void addAuthn(SaajSoapMessage request) throws TransformerException {
Transformer identityTransform = TransformerFactory.newInstance().newTransformer();
DOMResult domResult = new DOMResult();
identityTransform.transform(request.getPayloadSource(), domResult);
Node bodyContent = domResult.getNode();
Document doc = (Document) bodyContent;
doc.getFirstChild().appendChild(authNode(doc));
identityTransform.transform(new DOMSource(bodyContent), request.getPayloadResult());
}
protected Node authNode(Document doc) {
Element authentication = doc.createElementNS(ns, "authentication");
Element username = doc.createElementNS(ns, "username");
username.setTextContent(authn.getUsername());
Element password = doc.createElementNS(ns, "password");
password.setTextContent(authn.getPassword());
authentication.appendChild(username);
authentication.appendChild(password);
return authentication;
}
This solution was used because the WebServiceMessageCallback would require me to change the Document, and the SaajSoapMessageFactory is activated before the soap body has been inserted by the configured Jaxb2Marshaller.
I realized that it was easer to alter the request at a later point. I did not need to modify the original SOAP message, so long as I was able to modify the data before it reached my endpoint.
The endpoints I am working with all extend AbstractDom4jPayloadEndpoint - so I wrapped these endpoints in a proxy that allowed me to modify the request element before proceeding to my endpoint. i.e.:
public class MyProxyEndpoint extends AbstractDom4jPayloadEndpoint
#Override
protected Element invokeInternal(
Element requestElement,
Document responseDocument ) throws Exception
{
if( requestElement != null )
{
// alter request element
}
return ( Element ) this.invokeMethod.invoke(
this.target,
requestElement,
responseDocument );
}

JAX-WS Password Type PasswordText

I've got a simple command line Java JAX-WS app to test a SOAP request, but the server is expecting the Password Type to be PasswordText and I'm stumped on how to set this...
The code looks like so:
#WebServiceRef
private static final HelloService helloService = new HelloService(url, new QName(
URL, "HelloService"));
public static void main(final String... args) {
try {
final HelloPort helloPort = helloService.getHelloPort();
final BindingProvider hB = ((BindingProvider) helloPort);
hB.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
END_POINT_ADDRESS);
hB.getRequestContext().put(BindingProvider.USERNAME_PROPERTY,
USERNAME);
hB.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY,
PASSWORD);
...
I've tested the request using SOAP-UI so I know it's working. Any help on setting the password type would be appreciated.
Thanks.
That will set the username and password for Basic HTTP authentication. If you've tested it in SoapUI, I'm guessing the 'PasswordText' value you speak of is the 'WSS-Password Type' in the request details pane. That sets WSS security, not HTTP security.
With JAX-WS in Java6 you need to attach a SOAPHandler to inject the WSS-Usertoken into the SOAP Header. There are plenty of bits and bobs about this round the net, but I couldn't find one single link to post, so here's some code instead to get you going...
To add a handler you need something like:
final Binding binding = ((BindingProvider) servicePort).getBinding();
List<Handler> handlerList = binding.getHandlerChain();
if (handlerList == null)
handlerList = new ArrayList<Handler>();
handlerList.add(new SecurityHandler());
binding.setHandlerChain(handlerList); // <- important!
Then the SecurityHandler class will do the deed. Handlers are general things and get called for both successful messages and for faults, but perhaps more importantly they get called in both message directions - for the outgoing request and then again for the incoming response. You only want to handle outgoing messages. So you'll need something like:
public final class SecurityHandler implements SOAPHandler<SOAPMessageContext> {
...
#Override
public boolean handleMessage(final SOAPMessageContext msgCtx) {
// Indicator telling us which direction this message is going in
final Boolean outInd = (Boolean) msgCtx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
// Handler must only add security headers to outbound messages
if (outInd.booleanValue()) {
try {
// Get the SOAP Envelope
final SOAPEnvelope envelope = msgCtx.getMessage().getSOAPPart().getEnvelope();
// Header may or may not exist yet
SOAPHeader header = envelope.getHeader();
if (header == null)
header = envelope.addHeader();
// Add WSS Usertoken Element Tree
final SOAPElement security = header.addChildElement("Security", "wsse",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
final SOAPElement userToken = security.addChildElement("UsernameToken", "wsse");
userToken.addChildElement("Username", "wsse").addTextNode("MyWSSUsername");
userToken.addChildElement("Password", "wsse").addTextNode("MyWSSPassword");
} catch (final Exception e) {
LOG.error(e);
return false;
}
}
return true;
}
...
// Other required methods on interface need no guts
}
I've made a few assumptions here, but hopefully it'll get you going!
Kind regards.
If you implement SOAPHandler interface, the method msgCtx.getMessage() will render the entire XML, and if you are working with big files you will have Out of Memory errors. I tested with UsernameToken authentication on JAX-WS client and it works:
String SECURITY_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
String PASSWORD_TYPE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
String AUTH_PREFIX = "wss";
MyService service = new MyService();
MyServicePort port = service.getMyServicePort();
try {
SOAPFactory soapFactory = SOAPFactory.newInstance();
SOAPElement security = soapFactory.createElement("Security", AUTH_PREFIX, SECURITY_NS);
SOAPElement uToken = soapFactory.createElement("UsernameToken", AUTH_PREFIX, SECURITY_NS);
SOAPElement username = soapFactory.createElement("Username", AUTH_PREFIX, SECURITY_NS);
username.addTextNode("username");
SOAPElement pass = soapFactory.createElement("Password", AUTH_PREFIX, SECURITY_NS);
pass.addAttribute(new QName("Type"), PASSWORD_TYPE);
pass.addTextNode("password");
uToken.addChildElement(username);
uToken.addChildElement(pass);
security.addChildElement(uToken);
Header header = Headers.create(security);
((WSBindingProvider) port).setOutboundHeaders(header);
// now, call webservice
} catch (SOAPException ex) {
ex.printStackTrace();
}
Edit: You should add the "rt.jar" from jre to classpath.

Categories