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).
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).
9 Comments:
I think this is a very good and usefull example.
What would be nice is to extends this example a little further so that you have 2 user that each can access a specific web method based on an User's role. A simple testcase could prove the positive and negative tests.
Thnx
André
i've a problem.. when i call teh service i get this Caused by: javax.xml.stream.XMLStreamException: NamespaceURI cannot be null
at com.sun.xml.internal.stream.writers.XMLStreamWriterImpl.writeAttribute(Unknown Source)
at org.codehaus.xfire.util.STAXUtils.writeElement(STAXUtils.java:366)
at org.codehaus.xfire.util.STAXUtils.writeNode(STAXUtils.java:391)
can you please help me??
tnx a lot!!
Hi
I tried your xfire-acegi example but I can't get it to work, it gives me the error message - "Authentication Security Context lost in message transport?". Do you have a complete client example that I can use? I ma using Geronimo 1.1.1, xfire 1.2.4, spring 2.0.4.
Thanks,
Richard
Setting an Acegi token in the security context is only adding the token to a local thread variable. If you only do that and nothing else, the token will only be availalbe on that local thread instance and will be dereferenced when that thread terminates. This works well from the client perspective as it allows you to transparently propagate the token to a WS-S username token header in an outbound SOAP message. However, you need to have a matching implementation on the server side that uses an X-Fire InHandler to read the token in from the SOAP header, process it, and add it into the servers Acegi security context (if the credential is valid) for subsequent processing. If you do not do this, the token will not be avaiallbe on the server side (in the server JVM) using just the code sample described in this article.
Hello Michael,
Great post. Thanks. I have a question, our clients (external) can invoke our webservvices from either Java, .NET or any other platform. Will your approach work for a client different from Java.
Thanks
Matt
it is really helpful, I wonder if the client side, who need consumes this service, is developed by dotnet or other java project like aixs, how can i generate a similar header for authentation?
thank you very much.
as acegi in the security for projects is used that work with xfire?
Ignore Richard's comment about "Authentication Security Context lost in message transport?" A look at the JUnit test shows that's an expected result because the context has been cleared.
great, realy great.
Post a Comment
<< Home