Apache CXF is a great library for providing web services from Java. Their integration with the spring framework is the best of any web services library I have seen. But when it comes to documentation, they are a bit lacking in a few areas. Some tasks that you would think would be part of the framework are simply not provided.

There are two approaches to providing security for Web Services. You can secure the protocol itself (HTTP based security) or you can secure the web services call itself (the actual soap message). The new web services standard around security have opted to take the second approach. Web services and SOAP are designed to be separate from the protocol. The WS-Security specifications deal with securing the SOAP packet itself rather than relying on security provided by the underlying protocol (in many cases HTTP).

Apache CXF provides a WS-Security implementation for handling security and authentication for web services. They also provide a mechanism for setting up HTTPs to provide a secured HTTP connection. The one thing they do not provide is a mechanism to authenticate over HTTP. If you have the luxury to use WS-Security then that is not a problem. For a project I was working on, I needed to provide a web service older clients could access that may not know about WS-Security. The solution was to use HTTP Basic Authentication.

Apache CXF provides access to the AuthorizationPolicy from any message. This gives access to the authentication information used for HTTP. The approach I took was to create a CXF interceptor to intercept the message and check this information for HTTP Basic Authentication parameters. If an error occurs then a SOAP fault is thrown.

The problem with this approach was that a fault is returned to the client as an HTTP 500 response code. What we really want here is to use the 401/403 mechanism as a true web server would for HTTP based authentation. I found the following article on the CXF mailing lists that explained how to set some of that up.

http://mail-archives.apache.org/mod_mbox/incubator-cxf-dev/200702.mbox/

%3CFA1787F64A095C4090E76EBAF8B183E071FC0A@owa-emea.iona.com%3E

I modified that code provide an CXF interceptor that provides HTTP Basic Authentication. When a message is received the HTTP headers are checked. If no user/password is provided, a 401 is returned. This indicates to the client the HTTP authentication is required. If the user/password is invalid, a 403 is returned indicating that this username/password is forbidden to access the service.

Here is an excerpt from the code. I took out some of the implementation details and left the basic “meat” of how to do this from CXF.


public class BasicAuthAuthorizationInterceptor extends 
        SoapHeaderInterceptor {
    private Map<String,String users;
    protected Logger log = Logger.getLogger(getClass());
    @Override public void handleMessage(Message message) 
            throws Fault {
        // This is set by CXF
        AuthorizationPolicy policy = message.get(
            AuthorizationPolicy.class);
        // If the policy is not set, the user did not specify
        // credentials. A 401 is sent to the client to indicate
        // that authentication is required
        if (policy == null) {
            sendErrorResponse(message, 
                HttpURLConnection.HTTP_UNAUTHORIZED);
            return;
        }
        // Verify the password
        String realPassword = getAcualPassword(
            policy.getUserName());
        if (realPassword == null || 
                !realPassword.equals(policy.getPassword())) {
            log.warn("Invalid username or password for user: " 
                + policy.getUserName());
            sendErrorResponse(message, 
                HttpURLConnection.HTTP_FORBIDDEN);
        }
    }
    private void sendErrorResponse(Message message, 
            int responseCode) {
        Message outMessage = getOutMessage(message);
        outMessage.put(Message.RESPONSE_CODE, 
            responseCode);
        // Set the response headers
        Map responseHeaders = message.get(
            Message.PROTOCOL_HEADERS);
        if (responseHeaders != null) {
            responseHeaders.put("WWW-Authenticate", 
                Arrays.asList(new String[]{"Basic realm=realm"}));
            responseHeaders.put("Content-Length", 
                Arrays.asList(new String[]{"0"}));
        }
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            close(outMessage);
        } catch (IOException e) {
            log.warn(e.getMessage(), e);
        }
    }
    private Message getOutMessage(Message inMessage) {
        Exchange exchange = inMessage.getExchange();
        Message outMessage = exchange.getOutMessage();
        if (outMessage == null) {
            Endpoint endpoint = exchange.get(Endpoint.class);
            outMessage = endpoint.getBinding().createMessage();
            exchange.setOutMessage(outMessage);
        }
        outMessage.putAll(inMessage);
        return outMessage;
    }
    private Conduit getConduit(Message inMessage) 
            throws IOException {
        Exchange exchange = inMessage.getExchange();
        EndpointReferenceType target = exchange.get(
            EndpointReferenceType.class);
        Conduit conduit =
            exchange.getDestination().getBackChannel(
            inMessage, null, target);
        exchange.setConduit(conduit);
        return conduit;
    }
    private void close(Message outMessage) 
            throws IOException {
        OutputStream os = outMessage.getContent(
            OutputStream.class);
        os.flush();
        os.close();
    }
}