Basic Authentication with SOAP Web Service

Nothing new this time around, but I thought I’ll post an entry anyway, more as a reminder for myself in case I need it again.

Every now and then, you would need to invoke a web service end point using basic authentication. I am sure modern frameworks, such as JAX-WS (MKyong 2010), can do this easily.

However, in the unfortunate case you ever find that you would need to manually do this, the formula is to add a header entry as follows (Wikipedia 2012).

Header Value
Authorization Basic (followed by username + ‘:’ + password, encoded in base64)

Assuming that the end point service you would like to invoke has the credentials of username being adumbledore and password being Sherbet Lemon (if you don’t understand, suggest read Harry Potter), then first thing you would want to do is to structure it like this:

adumbledore:Sherbet Lemon

Then, use base64 encoder to encode it. For example, google would suggest this.

Then all you need to do is to add the Authorization header. See the image below.

References:

MKyong, 2010, Application Authentication with JAX-WS, accessed 14 May 2012.

Opinionatedgeek.com, 2012, Base 64 encoder, accessed 14 May 2012.

Wikipedia, 2012, Basic access authentication, accessed 14 May 2012.

JAX-WS, wsimport, and the error “MustUnderstand headers … not understood”

Following the previous adventure surrounding collision in the object factory class, this time around we take it a step further. Instead of simply using xjc command from JAXB and marshall/unmarshall elements into the SOAP envelope, we thought lets use wsimport against the WSDL instead. Again, wsimport is part of standard JDK.

The tool wsimport also makes use of xjc and therefore the collision issue also occurred. Thankfully, we can specify the binding (-b) argument with wsimport as well. Even better, use the following maven plugin:

<plugin>
    <groupId>org.jvnet.jax-ws-commons</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <version>2.1</version>
    <executions>
        <execution>
            <goals>
                <goal>wsimport</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <target>2.1</target>
        <extension>true</extension>
        <bindingDirectory>src/main/resources</bindingDirectory>
        <bindingFiles>
            <bindingFile>bindings.xjb</bindingFile>
        </bindingFiles>
        <wsdlDirectory>src/main/resources</wsdlDirectory>
        <wsdlFiles>
            <wsdlFile>schemas.ppsr.gov.au.2011.04.services.wsdl</wsdlFile>
        </wsdlFiles>
    </configuration>
</plugin>

All is well and good now.

When we actually hit the end point, the following error is thrown:

27/03/2012 6:40:20 PM com.sun.xml.internal.ws.protocol.soap.MUTube getMisUnderstoodHeaders
INFO: Element not understood={http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security
Exception: MustUnderstand headers:[{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security] are not understood

The interesting thing is that invoking using soapUI, all worked okay, and the following response returned:


<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"
            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-1">
                <wsse:Username><!-- omitted --></wsse:Username>
                <wsse:Password 
                    Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
                    <!-- omitted -->
                </wsse:Password>
                <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">
                    <!-- omitted -->
                </wsse:Nonce>
                <wsu:Created>2012-03-27T07:40:19.628Z</wsu:Created>
            </wsse:UsernameToken>
        </wsse:Security>
    </S:Header>
    <S:Body>
        <ns2:PingRequestMessage
            xmlns="http://schemas.ppsr.gov.au/2011/04/data"
            xmlns:ns2="http://schemas.ppsr.gov.au/2011/04/services"
            xmlns:ns3="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
            xmlns:ns4="http://schemas.ppsr.gov.au/faults" />
    </S:Body>
</S:Envelope>

After further reading, we discover it’s mostly related to the mustUnderstand and the way we use javax.xml.ws.handler.soap.SOAPHandler (White 2010). Reading javadoc itself is not enough, since the getHeaders() method simply says “Gets the header blocks that can be processed by this Handler instance” (Oracle 2011).

Therefore, to resolve this issue, our handler must understand the response, and that’s done by “handling” the response as follows:

PPSRSOAPMessageHandler

package com.wordpress.dwuysan;

import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

public class PPSRSOApMessageHandler implements SOAPHandler<SOAPMessageContext> {
    // ... the rest of the code omitted

    @Override
    public Set<QName> getHeaders() {
        final QName securityHeader = new QName(
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
            "Security",
            "wsse");

        // ... "understand" the response, very complex logic goes here
        // ... "understand" the response, very complex logic goes here
        // ... "understand" the response, very complex logic goes here

        final HashSet headers = new HashSet();
        headers.add(securityHeader);

        // notify the runtime that this is handled
        return headers;
    }

    // ... the rest of the code omitted
}

References:
Oracle, 2011, SOAPHandler (Java EE 6), accessed 02 April 2012.

White, J, 2010, Working with Headers in JAX-WS SOAPHandlers, accessed 02 April 2012.