Saturday, May 15, 2010

Using WS Security for SOAP Requests in ColdFusion

UPDATE: I've posted the source on GitHub: https://github.com/anthony-id/cfWSAuthenticator

Late last year, I had need to use a third party API that required WS Authentication. I had the WSDLs, I could get it to work in
SOAP UI but when I tried to get it to work in my code it just wasn't happening. It turns out that WS Authentication works out of the box with Axis 2, but ColdFusion creates all its web services with Axis 1, so I had to create the client myself.

After quite a bit of research, it turns out this is quite easy using a couple of Java libraries and ColdFusion's "CreateObject" function. Here's how I got it to work:

First download the following libraries:

xmlsec-1.4.2.jar - http://www.aleksey.com/xmlsec/

An updated version is available, but this is the one I have working

wss4j-1.5.8.jar - http://ws.apache.org/wss4j/
This is the library that does most of the work - Rampart is the portion that Axis 2 uses for this, but we needed to just utilize the signing portions for our purposes.


The libraries need to go to [cf_home]/lib or be added to your classpath. A server restart may be necessary for ColdFusion to be able to see them.


Now the jars are available for use. I created two functions to get these to work - one to do the signing and one to send the request. I have a component in which these live, but you could do the whole thing inline if you wanted to.

The one downside to this is that you'll need to construct the SOAP requests manually. This isn't hard if you use something like SOAP UI - I just grab the XML from there and modify as necessary. I place the SOAP request inside a cfxml block.


<cffunction name="addWSAuthentication" access="public" output="false" hint="I sign SOAP envelope using WS Authentication">
<cfargument name="soapEnvelope" type="string" required="true">
<cfargument name="username" type="string" required="true">
<cfargument name="password" type="string" required="false">
<cfscript>
// Create Java Objects from xmlsec and wss4j
var WSConstants = CreateObject("Java","org.apache.ws.security.WSConstants");
var msg = CreateObject("Java","org.apache.ws.security.message.WSSAddUsernameToken");
// Get Soap Envlope document for Java processing
var soapEnv = arguments.soapEnvelope;
var env = soapEnv.getDocumentElement();
var e = "";
// Set Password type to TEXT (default is DIGEST)
msg.setPasswordType(WSConstants.PASSWORD_TEXT);
// Create WS-Security SOAP header using the build method from WSAddUsernameToken
e = msg.build(env.GetOwnerDocument(),arguments.username,arguments.password);
// Add the Nonce and Created elements
msg.addNonce(e);
msg.addCreated(e);
// Return the secure xml object
return soapEnv;
</cfscript>
</cffunction>


This function sends the soap request and returns the result. The endpoint and SOAP Action can be found in the WSDL or by using SOAP UI. In SOAP UI, look at the raw post, the SOAP Action is in the headers. Also note the additional headers in the cfhttpparams - these are necessary to make the WS Authentication work.

<cffunction name="sendSoapRequest" access="public" output="false" hint="I send the SOAP request off retrun the SOAP response">
<cfargument name="endpoint" type="string" required="true">
<cfargument name="soapEnvelope" type="any" required="true">
<cfargument name="soapAction" type="string" required="false" default="">
<cfset result="">
<cfset soapenv="">

<cfhttp url="#arguments.endpoint#" method="POST" result="result">
<cfhttpparam type="Header" name="Accept-Encoding" value="deflate;q=0">
<cfhttpparam type="Header" name="TE" value="deflate;q=0">
<cfhttpparam type="header" name="SOAPAction" value="">
<cfhttpparam type="xml" value="#toString(xmlParse(arguments.soapEnvelope))#">

<cfreturn>
</cfreturn>
</cffunction>


Hopefully this will save you some time if you ever have to do WS Security in ColdFusion. It took me a couple of weeks of trial and error to get this working, but I'm now using it for two different third party APIs without a hitch.

Acknowledgments
The following were critical in helping me get this solved:
Ben Nadel's Blog - Listing All Classes In A Jar File Using ColdFusion And CFZip
Steven Erat's Blog - Workaround for CFHTTP and Compressed HTTP Response from IIS


Sunday, May 2, 2010

CF.Objective() Pecha Kucha Presentaion

Here is my Pecha Kucha presentation from CF.Objective().

What is Pecha Kucha? In a sentence, it's a talk about anything you're interested in that lasts six minutes and forty seconds. Why 6:40? Because that's 20 slides for 20 seconds each that automatically advance. Ignite is based on Pecha Kucha with the primary difference being those slides advance every 15 seconds.

I hope to get the slides up here as well. Until then, enjoy and let me know what you think. I had a great time presenting and hope to get a chance to do it again!

Head over to Bob Silverberg's Blog to see all the PK talks.

All the talks in succession are also posted on YouTube as a playlist.