Thursday 18 October 2012

How to invoke secured JAX-WS web service from a standalone client


     
       In this post I will explain the procedure of invoking secured JAX-WS web service from a standalone java client. It explains the problems you may face during the process.
There are several ways by which you can invoke a secured web service. I will explain it in two ways here. In this example I have a sample web service which is protected by the policy : wss_saml_or_username_token_service_policy.
I will not get the desired response if I try to invoke the protected web service without providing a proper username token in the soap header.
In weblogic, you can attach policies to web services with the help of owsm. Following block shows the snippet to be present in the soap header in order to get the client asserted by the web service provider which is protected by wss_saml_or_username_token_service_policy policy :

<S:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" S: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-Kr9QjWbqpQgxYI4CDWNxCg22">
<wsse:Username>administrator</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">Passw0rd</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</S:Header>

The username in this case is 'administrator' and password is 'Passw0rd'

Here are the two ways in which you can add the above block in the header of the soap request on the client side :
1. First way is to add credentials in the RequestContext of the client port :

      List<CredentialProvider> credProviders =
          new ArrayList<CredentialProvider>();
      String username = "administrator";
      String password = "Passw0rd";
      CredentialProvider cp =
          new ClientUNTCredentialProvider(username.getBytes(),
                                          password.getBytes());
      credProviders.add(cp);
      Map<String, Object> requestContext =
          ((BindingProvider)sampleWebServicePort).getRequestContext();
      requestContext.put(BindingProvider.USERNAME_PROPERTY,username);
      requestContext.put(BindingProvider.PASSWORD_PROPERTY,password);
      sampleWebServicePort.callService();
     

2.  Second way is add to create the soap security header object which is to be added in the HandlerChain :

        try {
            CustomSOAPHandler sh = new CustomSOAPHandler();
            List<Handler> new_handlerChain = new ArrayList<Handler>();
            new_handlerChain.add(sh);
            ((BindingProvider)sampleWebServicePort).getBinding().setHandlerChain(new_handlerChain);
sampleWebServicePort.callService();
        } catch (Throwable e) {
            e.printStackTrace();
        }

Create a custom SOAPHandler class which will add the header in the soap request.
CustomSOAPHandler:


public class CustomSOAPHandler implements SOAPHandler<SOAPMessageContext> {

    private static final String AUTH_PREFIX = "wsse";
    private static final String AUTH_NS =
        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
.
.
.


    public boolean handleMessage(SOAPMessageContext context) {

        try {
            SOAPEnvelope envelope =
                context.getMessage().getSOAPPart().getEnvelope();
            SOAPFactory soapFactory = SOAPFactory.newInstance();
            SOAPElement wsSecHeaderElm =
                soapFactory.createElement("Security", AUTH_PREFIX, AUTH_NS);
            Name wsSecHdrMustUnderstandAttr =
                soapFactory.createName("mustUnderstand", "S",
                                       "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            wsSecHeaderElm.addAttribute(wsSecHdrMustUnderstandAttr, "1");
            SOAPElement userNameTokenElm =
                soapFactory.createElement("UsernameToken", AUTH_PREFIX,
                                          AUTH_NS);
            Name userNameTokenIdName =
                soapFactory.createName("id", "wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
            userNameTokenElm.addAttribute(userNameTokenIdName,
                                          "UsernameToken-ORbTEPzNsEMDfzrI9sscVA22");
            SOAPElement userNameElm =
                soapFactory.createElement("Username", AUTH_PREFIX, AUTH_NS);
            userNameElm.addTextNode("administrator");
            SOAPElement passwdElm =
                soapFactory.createElement("Password", AUTH_PREFIX, AUTH_NS);
            Name passwdTypeAttr = soapFactory.createName("Type");
            passwdElm.addAttribute(passwdTypeAttr,
                                   "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
            passwdElm.addTextNode("Passw0rd");
            userNameTokenElm.addChildElement(userNameElm);
            userNameTokenElm.addChildElement(passwdElm);
            wsSecHeaderElm.addChildElement(userNameTokenElm);
            if (envelope.getHeader() == null) {
                SOAPHeader sh = envelope.addHeader();
                sh.addChildElement(wsSecHeaderElm);
            } else {
                SOAPHeader sh = envelope.getHeader();
                sh.addChildElement(wsSecHeaderElm);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return true;
    }

In this method, we are creating the Security element in the header of the soap request and on the server side it gets asserted successfully.
If the credentials are proper, then your service will get executed else it will throw an exception saying that the username token cannot be validated.


The second way does not require any extra jars to be present in the classpath whereas in first way you will need to add some weblogic jars in classpath in order to get it working.
The problem with the second way is if you try to test it with the help of jdeveloper, you will get the following error :

Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: Unable to add security token for identity, token uri =http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID
at com.sun.xml.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:197)
at com.sun.xml.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:122)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:125)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:95)

If you analyze the stack trace carefully, you will notice that in this case jdeveloper uses classes from jars (glassfish jars) which are not part of jdk. And that's why you get the strange exception. If you run the same code from eclipse or through command line with just jdk, it will work.


8 comments:

Anonymous said...

CustomerSOAPHandler was very useful for secured web service call.. and the note on exception jdeveloper exception saved a lot of time.. thank u!!

Anonymous said...

Hi,
Really Helpful post.Am trying to add wsse header generated code(stub).

org.apache.axiom.soap.SOAPEnvelope env = null;

env = toEnvelope(getFactory(_operationClient.getOptions().getSoapVersionURI()),
opr0,
optimizeContent(new javax.xml.namespace.QName("http://www.test.com/ser",
"opr")), new javax.xml.namespace.QName("http://www.test.com/ser",
"opr"));

//adding SOAP soap_headers
_serviceClient.addHeadersToEnvelope(env);
// set the message context with that soap envelope
_messageContext.setEnvelope(env);

// add the message contxt to the operation client
_operationClient.addMessageContext(_messageContext)

Ganesh Kamble said...

Hi,
You will have to create the similar element in the axis SOAP header. Follow this link :
http://www.nsftools.com/stubby/SOAPHeaders.htm

Prateek Agarwal said...

Hey I had a same requirement for @Policy(uri = "policy:Wssp1.2-2007-Https-BasicAuth.xml" , attachToWsdl=false). I used a SOAP handler to append my HTTP Header @ Server side, but the call never came from the client to handler if the HTTP header with Basic Auth was not present.

public boolean handleMessage(SOAPMessageContext context) {
final Boolean isOutbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

if(!isOutbound){
try{
final SOAPMessage message = context.getMessage();
String authorization = Base64Coder.encodeString("uname:pass");
MimeHeaders hd = message.getMimeHeaders();
hd.addHeader("Authorization", "Basic " + authorization);
System.out.println("Included Auth Header");
}catch(Exception se){
throw new RuntimeException(se);

}
}
return true;
}

and in EJB I mapped the handler using

@HandlerChain(file="../com/handler/handlers.xml")

and my handlers.xml contains



AuthSoapHandler
com.handler.AuthSoapHandler



If i use HTTP Basic auth in my SOAP call from Client is works like a charm, but not when I append it on Server side using SOAP Handler

Any pointers?

Ganesh Kamble said...

Are you using openjdk?
Try changing the java version.

Prateek Agarwal said...

Can you help me understand how will that help me make the call to Handler?

Unknown said...

This is really a helpful post!!!

Anand Kumar Oracle DBA said...

Hi..THis link is really healful, can you please share the jar file as we are planning to use option 1.