In this article I will add a security header to the soap request. So I will create a class implementing the interface SOAPHandler and I will override the method handleMessage:
Now, I will add a simple call to this class in my implementation client:
@Override
public boolean handleMessage(SOAPMessageContext context) {
String prefixUri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-";
String uri = prefixUri + "wssecurity-secext-1.0.xsd";
String uta = prefixUri + "wssecurity-utility-1.0.xsd";
String ta = prefixUri + "username-token-profile-1.0#PasswordText";
Boolean outboundProperty =
(Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
try {
SOAPEnvelope envelope = context.getMessage()
.getSOAPPart().getEnvelope();
SOAPFactory factory = SOAPFactory.newInstance();
String prefix = "wsse";
SOAPElement securityElem =
factory.createElement("Security",prefix,uri);
SOAPElement tokenElem =
factory.createElement("UsernameToken",prefix,uri);
tokenElem.addAttribute(QName.valueOf("wsu:Id"),"UsernameToken-2");
tokenElem.addAttribute(QName.valueOf("xmlns:wsu"), uta);
SOAPElement userElem =
factory.createElement("Username",prefix,uri);
userElem.addTextNode("myUser");
SOAPElement pwdElem =
factory.createElement("Password",prefix,uri);
pwdElem.addTextNode("myPwd");
pwdElem.addAttribute(QName.valueOf("Type"), ta);
tokenElem.addChildElement(userElem);
tokenElem.addChildElement(pwdElem);
securityElem.addChildElement(tokenElem);
SOAPHeader header = envelope.addHeader();
header.addChildElement(securityElem);
} catch (Exception e) {
System.out.println("Exception in handler: " + e);
}
}
return true;
}
Now, I will add a simple call to this class in my implementation client:
service.setHandlerResolver(new HandlerResolver() {
@Override
public List<Handler> getHandlerChain(PortInfo portInfo) {
List<Handler> handlerList = new ArrayList<Handler>();
handlerList.add(new MySoapHandler());
return handlerList;
}
});
Thanks Moez,
ReplyDeleteYour solution is generic, if you want to deal only with security header you can use ws*-security like :
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class ClientPasswordCallback implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if ("user".equals(pc.getIdentifier())) {
pc.setPassword("password");
} // else {...} - can add more users, access DB, etc.
}
}
and
Map outProps = new HashMap();
Client client = org.apache.cxf.frontend.ClientProxy.getClient(port);
Endpoint cxfEndpoint = client.getEndpoint();
// Manual WSS4JOutInterceptor interceptor process
outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
outProps.put(WSHandlerConstants.USER, "user");
outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS,
ClientPasswordCallback.class.getName());
outProps.put(WSHandlerConstants.TIMESTAMP, new Date());
outProps.put(WSHandlerConstants.MUST_UNDERSTAND, "0");
WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
List> outInterceptors = cxfEndpoint.getOutInterceptors();
outInterceptors.add(wssOut);
Awesome man...works like a charm...
ReplyDeleteWhen I tried this, I got the following exception: com.sun.xml.ws.message.saaj.SAAJHeader cannot be cast to com.sun.xml.ws.security.opt.impl.outgoing.SecurityHeader
ReplyDeleteI'm using a standalone java client. I tried JDK 6 and JDK 7. Any ideas? Thanks!
which server you use? It's a problem of lib versions
ReplyDeleteI've got it working using just JDK 6 now. It turns out that if I have the various webservices-* jars from Metro in my classpath, I get that error. When I remove them, the code works fine. That's great, except that my application needs to run under Tomcat 6 and both host web services and be a client for web service calls. I can't host without the webservices-* jars. I can't be a client with the webservices-* jars. Very, very frustrating.
Deleteeven I am facing the same problem. It works fine with standalone application. But when I am using in webservice it is giving same error (com.sun.xml.ws.message.saaj.SAAJHeader cannot be cast to com.sun.xml.ws.security.opt.impl.outgoing.SecurityHeader).
DeleteWhat I figured out is that there is a difference when to how to do this when you are running in a web container. You can not do the above in the web container. Do the following and it will work like a charm.
DeleteHow to do WSIT validation when calling a web service:
1) Create the user id/password call back handler class. Example below.
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
public class SecurityHandler implements CallbackHandler
{
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException
{
for (int i = 0; i < callbacks.length; i++)
{
if (callbacks[i] instanceof NameCallback)
{
String userID = "set this however you want";
NameCallback nc = (NameCallback) callbacks[i];
nc.setName(userID);
}
else
{
if (callbacks[i] instanceof PasswordCallback)
{
password = "set this however you want";
PasswordCallback pc = (PasswordCallback) callbacks[i];
pc.setPassword(password.toCharArray());
}
else
{
throw new UnsupportedCallbackException(callbacks[i], callbacks[i].getClass().getName());
}
}
}
}
}
2) Go to the WSDL url and copy the WSDL to a file named "xxxxxx.xml". xxxxxx should be the name of the web service.
3) Modify the newly created xml file. Need to replace the existing wsp:Policy section with one similar to the following, specifying the callback handler for user id/password.
4) Also in the newly created xml file, add the following to the first line (wsdl:definitions). Take note of the targetNamespace on that line as well.
xmlns:sc="http://schemas.sun.com/2006/03/wss/client" xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy"
5) Modify the wsit-client.xml file. Add a new line as follows, right before the last line of the file:
Step 3 above removed the xml. Sorry
ReplyDelete<wsp:Policy wsu:Id="BasicHttpBinding_Ixxxxxx_policy">
<wsp:ExactlyOne>
<wsp:All>
<sc:CallbackHandlerConfiguration wspp:visibility="private">
<sc:CallbackHandler classname="your.project.SecurityHandler" name="usernameHandler"/>
<sc:CallbackHandler classname="your.project.SecurityHandler" name="passwordHandler"/>
</sc:CallbackHandlerConfiguration>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
Step 5 as well:
ReplyDelete<import location="xml file name from step 2 above" namespace="targetNamespace from step 4 above"/>
Hi all,
ReplyDeletefacing the same problem as @Bill Gottlieb :
com.sun.xml.ws.message.saaj.SAAJHeader cannot be cast to com.sun.xml.ws.security.opt.impl.outgoing.SecurityHeader
when running form within Payara server.
Desktop Java app runs just fine.
Any idea?