I’ve written a JAX-WS handler to add a WS-Security header to my SOAP client’s outbound messages:
package com.soap.client;
import javax.xml.namespace.QName;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
public class ClientHeaderHandler implements SOAPHandler<SOAPMessageContext> {
private static final String WSSECURITY_PREFIX = "wsse";
private static final String WSSECURITY_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
private static final String PASSWORD_TEXT_TYPE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText";
/**
* {@inheritDoc}
* @see javax.xml.ws.handler.Handler#handleMessage(javax.xml.ws.handler.MessageContext)
*/
@Override
public boolean handleMessage(final SOAPMessageContext context) {
boolean outbound = false;
outbound = (Boolean) context.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outbound) {
try {
addSecurityHeader(context);
} catch (SOAPException e) {
// do nothing
}
}
return true;
}
private void addSecurityHeader(final SOAPMessageContext context) throws SOAPException {
SOAPFactory sf = SOAPFactory.newInstance();
SOAPElement securityElem = sf.createElement("Security", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
SOAPElement tokenElem = sf.createElement("UsernameToken", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
SOAPElement usernameElem = sf.createElement("Username", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
usernameElem.addTextNode("myusername");
tokenElem.addChildElement(usernameElem);
Name passwordTypeName = sf.createName("Type", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
SOAPElement passwordElem = sf.createElement("Password", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
passwordElem.addAttribute(passwordTypeName, PASSWORD_TEXT_TYPE);
passwordElem.addTextNode("mypassword");
tokenElem.addChildElement(passwordElem);
securityElem.addChildElement(tokenElem);
context.getMessage().getSOAPPart().getEnvelope().addHeader().addChildElement(securityElem);
}
}
This mostly works; however, the WS-Security namespace and prefix are re-declared on each element they are used on (xmlns:wsse=http://…):
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Username xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">myusername</wsse:Username>
<wsse:Password wsse:Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">mypassword</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</S:Header>
<S:Body>
<MyBody/>
</S:Body>
</S:Envelope>
I’ve tried various combinations of QNames, Names, etc., but I can’t seem to make this work. What do I need to change so that the WS-Security namespace is only declared in the topmost Security element?
UPDATE: gpeche’s suggestion below worked for me. Switching from creating the element with a SOAPFactory then appending it via addChildElement to creating it directly via addChildElement:
private void addSecurityHeader(final SOAPMessageContext context) throws SOAPException {
SOAPFactory sf = SOAPFactory.newInstance();
SOAPElement securityElem = context.getMessage().getSOAPPart().getEnvelope().addHeader().addChildElement("Security", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
SOAPElement tokenElem = securityElem.addChildElement("UsernameToken", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
SOAPElement usernameElem = tokenElem.addChildElement("Username", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
usernameElem.addTextNode("myusername");
Name passwordTypeName = sf.createName("Type", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
SOAPElement passwordElem = tokenElem.addChildElement("Password", WSSECURITY_PREFIX, WSSECURITY_NAMESPACE);
passwordElem.addAttribute(passwordTypeName, PASSWORD_TEXT_TYPE);
passwordElem.addTextNode("mypassword");
}
generates much cleaner XML:
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>myusername</wsse:Username>
<wsse:Password wsse:Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">mypassword</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</S:Header>
<S:Body>
<MyBody/>
</S:Body>
</S:Envelope>
I’ve had the same problem and the only way I found to do that reliably was to regenerate the XML tree node by node:
However I keep thinking that there must be an easier way…
UPDATE:
Ok I think I see what your problem is: you are creating all
SOAPElements directly from theSOAPFactory. At creation timeSOAPElements have no parent set, so they cannot inherit the namespaces that you specified from anyone, and decide to write the namespace declaration themselves. Once thay have been created, they won’t bother checking atappend()/setParent()time if they can drop any declaration.Can you try creating only the outermost element from the
SOAPFactoryand then creating the rest viaSOAPElement.addChildElement()?