Tuesday, October 31, 2006

Propagating Acegi's Security Context in a WSS UsernameToken SOAP Header via XFire using wss4j

XFire includes a ws-security example, which demonstrates how a SOAP Header with a Web Service Security (WSS) UsernameToken can be inserted into an outgoing request message, using Apache's wss4j library. (The XFire ws-security example also demonstrates how to sign and/or encrypt a SOAP message, and how to use the WSS timestamp mechanism.)

I needed this functionality to be available more easily for Java service consumers, in the sense of an implicit security context passing transparent to the programmer when invoking a service. As Java API to set up a security context, Acegi from the Spring Framework should be used.

Maybe first some brief background on WSS: A WSS UsernameToken is really just a username and password, in principle similar to HTTP BASIC authentication. However HTTP BASIC delivers the credential at the transport level, whereas a WSS Header has the advantage of propagating a credential at the (SOAP) message level, thus allowing it to travel forward through several intermediaries. For example, a message could move from a service consumer to a XML security gateway, to an ESB and then into a message queue, out of which it would go into another ESB, which would route it to a service provider. With transport-level instead of message level propagation each intermediary would have to ensure forwarding, or out-of-message storage (queue) of the credential.

The WSS standard standardizes the SOAP headers used for such message level credentials; in addition to a UsernameToken, it could also be a X.509 certificate or a Kerberos ticket or a SAML token.

As a UsernameToken contains a cleartext password, the message would typically be protected either through transport level point-to-point encryption via SSL, just as would be advisable when using HTTP BASIC. Additionally, the SOAP message could also be protected through message level XML encryption.

So, back Java/Acegi/XFire: What I really wanted was to be able to do the following at the service consumer (client):

import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;

SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("uid", "pwd"));

myServiceStub.myOperation(myRequest);

SecurityContextHolder.clearContext();

and have the uid/pwd end up "transparently" in the outgoing message produced by XFire. The actual service provider (server) might or might not receive that header, e.g. if an intermediary (actual SOAP intermediary or just an in-process incoming message filter) authenticated and authorized the message, potentially stripping the WSS header. If however it did receive the SOAP header, it might have a need to get the credential back, again easily and using the same Acegi API, without having to deal with SOAP headers etc. directly, by doing:

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
assert auth != null; // Is null if no WSS Header present in SOAP message!
String uid = auth.getName();
String pwd = auth.getCredentials();

The acegi-ws-security-xfire-example contains a working example implementing just that. (I built it by extending the XFire jaxws-spring example I have contributed earlier to the project; but the Acegi/WSS integration itself is unrelated to that, and the Handler etc. infrastructure should be easily reusable in other uses of XFire, e.g. POJO without WSDL).