Sven Dens

deserialize me

SOAP headers in Flex and WS-Security

with 27 comments

UPDATE (06-16-2009): The problem with generating a correct nonce has been fixed!
It turned out to be a problem in the WSSEUsernameToken class in the com.adobe.crypto package in as3corelib.
A VERY big thank you goes to Tom from FlexibleFactory for pointing me to the solution and to Koen Weyn for fixing the bugs!

Dear readers,

I just spent 2 entire days trying to figure out how to consume a SOAP Webservice that requires a SOAP-header and WS-Security in Flex. Hopefully this post will help you out if you’re looking to do the same thing.

Download demo files here.

Let me start off by saying that the Import Web Service (WSDL) wizard in Flex 3 doesn’t work as it should!
The generated classes return invalid XML to the SOAP-request, and adding headers to your request is completely neglected.
But I did get it to work using the classical <mx:Webservice> tag and some hand-coding.

Here’s how to do it:

First you need a copy of the as3corelib (only if you need the WS-Security), more specifically you need the com.adobe.crypto.WSSEUsernameToken.as class.
This class returns a token as a String, which I think is not very convenient, so I added another function called “getUsernameTokenAsArray” which returns the different token elements required for WS-Security in an Array. This way you can just “pluck” the parts you need without having to parse a String.

Next you need to write some AS3 classes to map the datatypes expected by the WebService to your Flex app, and vice-versa.

I wrote a little utility class that lets you easily insert different types of SOAP-headers to your requests.
Currently methods are implemented for returning headers containing WS-Security. Feel free to use it and add your own methods for additional SOAP-header-types to it.
Here it is:

package be.svendens.util
{
    import mx.rpc.soap.SOAPHeader;
    import com.adobe.crypto.WSSEUsernameToken;

    public class SOAPHeaderUtil
    {
        private static const WSSE_SECURITY:QName = new QName( "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security" );
   
        public static function returnWSSEHeaderWithNonceAndTimestamp(username:String, password:String, nonce:String=null, timestamp:Date=null):SOAPHeader
        {
            var userToken:String    = "UsernameToken-"+Math.round(Math.random()*999999).toString();
            var wsseToken:Array     = WSSEUsernameToken.getUsernameTokenAsArray(username, password, nonce, timestamp);
            var headerXML:XML       = <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
                                        <wsse:UsernameToken wsu:Id={userToken} xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
                                            <wsse:Username>{wsseToken[0]}</wsse:Username>
                                            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">{wsseToken[1]}</wsse:Password>
                                            <wsse:Nonce>{wsseToken[2]}</wsse:Nonce>
                                            <wsu:Created>{wsseToken[3]}</wsu:Created>
                                        </wsse:UsernameToken>
                                    </wsse:Security>
           
            var header:SOAPHeader   = new SOAPHeader( WSSE_SECURITY, headerXML );
            return header;
        }
       
        public static function returnWSSEHeaderWithoutNonceAndTimestamp(username:String, password:String):SOAPHeader
        {
            var userToken:String    = "UsernameToken-"+Math.round(Math.random()*999999).toString();
            var wsseToken:Array     = WSSEUsernameToken.getUsernameTokenAsArray(username, password);
            var headerXML:XML       = <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
                                        <wsse:UsernameToken wsu:Id={userToken} xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
                                            <wsse:Username>{wsseToken[0]}</wsse:Username>
                                            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">{wsseToken[1]}</wsse:Password>
                                        </wsse:UsernameToken>
                                    </wsse:Security>
           
            var header:SOAPHeader   = new SOAPHeader( WSSE_SECURITY, headerXML );
            return header;
        }
    }
}

Now you can call your webservice and add headers like this:

var header:SOAPHeader = SOAPHeaderUtil.returnWSSEHeaderWithNonceAndTimestamp("username", "password");
myService.addHeader(header);

Basically you create an <mx:Webservice>, pass it the correct parameters for your webservice. Then create a function to add the headers.
Call the clearHeaders method of your webservice (it’s a default method for a webservice object) to clear any existing headers, next call the addHeader method to attach the new header.

Finally call your webservice operation with the necessary input parameters and there you go: a nice SOAP-envelope with headers and WS-Security!

You can download a complete demo containing all necessary classes and a sample MXML file here.

WSDL loaded
Invoking SOAP operation CreateCTAScheduleItem
Encoding SOAP request envelope
Encoding SOAP request body
'B5C37908-53AE-B85B-EDE1-3C3A54EB5255' producer sending message 'F9F59B36-51D7-DCC7-C673-3C3A59283DE9'
'direct_http_channel' channel sending message:
(mx.messaging.messages::SOAPMessage)#0
body = "
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <SOAP-ENV:Header>
        <wsse:Security SOAP-ENV:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <wsse:UsernameToken wsu:Id="UsernameToken-502651" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                <wsse:Username>Flex</wsse:Username>
                <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">mA5XaNPovDGiotMyb/e8A2fiIbU=</wsse:Password>
                <wsse:Nonce>MDU3NDc4OTIzODE2MjMzODc=</wsse:Nonce>
                <wsu:Created>2008-02-21T14:41:39.251Z</wsu:Created>
            </wsse:UsernameToken>
        </wsse:Security>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
        <schema:CreateCTAScheduleItemRequest xmlns:schema="http://yournamespace">
            <timePeriod>
                <StartDate>2008-02-21T13:41:38.654Z</StartDate>
                <EndDate>2008-02-21T13:41:38.654Z</EndDate>
            </timePeriod>
            <priority>4</priority>
            <cta>test</cta>
            <applicationURL>http://blog.svendens.be</applicationURL>
            <repeatInterval>2</repeatInterval>
        </schema:CreateCTAScheduleItemRequest>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>"

I’m still struggling with the authentication however.
The SOAP-request looks exactly as it should and validates fine by the WSDL, but when using the Nonce and Created elements my username/digest and nonce keep getting rejected by the server. Without the nonce everything works like a charm.
Any of you guru’s out there know how to tackle this last problem?

Written by Sven Dens

February 21st, 2008 at 5:00 pm

Posted in Flash, Flex, Java

27 Responses to 'SOAP headers in Flex and WS-Security'

Subscribe to comments with RSS or TrackBack to 'SOAP headers in Flex and WS-Security'.

  1. [...] a SOAP-header and WS-Security in Flex?Check out this most helpful post written by Sven over at Tested Unit. Technorati Tags: flex, soap, ws [...]

  2. I too am having trouble with using the web services introspection wizard and the proxy classes it generates to set SOAP header authentication data.

    I’m going to try your method, however I was really hoping to use the proxy classes generated by flex for my web service. Now I need to hand code all the stubs.

    This is really burning me out.

    Drop me a line if you’d like to chat.

    Rob

    Rob

    29 Feb 08 at 10:50 PM

  3. Hey Rob,

    Yep, it sure s*cks :-s
    Haven’t found a way yet to use the generated proxy classes (have not had the time to be exact, am working on a client project with a *VERY* tight deadline. My project should be finished by the end of the week, and then I’ll be doing some experiments to see if it’s possible to tweak the generated classes a bit so you could use them as they were meant to.
    Will keep you posted of the progress on this blog!

    Sven Dens

    3 Mar 08 at 4:19 PM

  4. The memory leaks are incredible! Have you tried calling the service 10 times and profile the memory? Any idea why?
    Thx

    Pedro

    12 Mar 08 at 2:11 PM

  5. For WSSEUsernameToken.getUsernameTokenAsArray there is no function in WSSEUsernameToken to return as an array, was this something that you manually created? Let me know, I’m still struggling with adding these silly headers myself.

    -Brian

    Brian

    10 Nov 08 at 7:49 PM

  6. Hi Sven,
    I have a particular problem with adding header, along with authentication params my server needs me to add namespace ns1 to it (Dont know frankly what it is for) so my evelope header looks something like..

    I dont know how to add xmlns:ns1=”promoregistration” part to the Header tag. Any idea?

    Sachin

    7 Jan 09 at 12:21 PM

  7. Hi Sachin,

    Your header code is missing from the comment I see.
    Anyway, a namespace is just an alias (a shortcut as you will) to a location where classes can be found.
    Suppose I have a package (in AS3) called com.svendens.examples and in that package I have 2 classes: Test1 and Test2.
    These classes represent 2 custom MXML-components I want to use in my views.

    Now, instead of having to type and in my application MXML, I can add a namespace to the -tag so I don’t have to type the whole path to the package where those classes reside.
    The mx in
    is the namespace mx that points to the location where the classes for the mx-namespace can be found at Adobe.
    (
    )

    If I add an “examples” namespace to the Application-tag as so: , I can now reference my custom components by just typing and .
    So that’s what a namespace is, in short, just an alias.

    Now, in your case, if you need to add something to the header, you can just edit the static functions in SOAPHeaderUtil.as to return a header that includes what you need.

    var headerXML : XML =

    {wsseToken[0]}
    {wsseToken[1]}
    {wsseToken[2]}
    {wsseToken[3]}

    ;

    Adding a namespace that is not being used makes no sense however, so if you don’t use a class from the ns1-namespace anywhere in the header-XML (by typing “ns1:SomethingIsActuallyUsedFromThisNamespace”, there’s no need adding the namespace.

    Sven

    Sven Dens

    7 Jan 09 at 12:49 PM

  8. Ok, I forgot all HTML is being stripped from the comments. Have a look at this old documentation page from Adobe to see what I’m talking about, I’m sure it’ll give you a better understanding of the subject.

    Sven Dens

    7 Jan 09 at 12:52 PM

  9. Hi,

    Have you found by now a way to do it with proxy classes? I know one way is to have the header defined in WSDL however our Java server side (xFire) doesn’t have good solution for that…

    Thanks

    guyt

    3 Feb 09 at 8:58 PM

  10. Hi Sven,

    I couldn’t find any information regarding the licensing terms of your code. Could you please provide a notice about the license under which you’re releasing this? Thanks!

    Ali Rantakari

    16 Feb 09 at 3:26 PM

  11. Hi Steve,
    I have used the security headers in my code and is working fine. However when running the web service for the second time in my
    application, I get “SOAP Response Version Mismatch” error. First time the service works
    fine and this error is not consistent. Any help as what might be the issue??
    Regards,
    Kiran.

    kiran

    13 Mar 09 at 6:07 AM

  12. I tried using your code and I am able to send username/password token to webservice.
    But at server side the token is simply rejected with reason as token cannot be verified.
    I am using CXF 2.1.3.
    If I use SPRING client everything works fine, but with flex I am still facing above problem.
    Can you please help, this is urgent issue.

    Curiousmind

    22 Mar 09 at 3:25 PM

  13. Sven,

    With your method, Can we access SOAP 1.2?

    Thanx

    Erik Ramalho

    25 Mar 09 at 1:34 PM

  14. The reason that it doesnt work with the nonce is because the Adobe code for writing the nonced data into the request gets the calculations the wrong way round, base64 encoding it at the wrong point if i remember correctly. There is a bug in the adobe bug base about this. Solution is to modify the code in the adobe.crypto package to calculate those values correctly as according to the spec.

    tom

    16 Jun 09 at 3:59 AM

  15. A description of the bug and an attached fix can be found in the as3corelib issues, here:

    http://code.google.com/p/as3corelib/issues/detail?id=25

    Worth a browse (http://code.google.com/p/as3corelib/issues/list) because other issues that may also impact on WSSE also contain attached fixes.

    Interesting to see that the bugs are still marked as status ‘new’ two years later, yet the fixes (which already exist) are yet to be rolled into the release package.

    I consider this to be a crime against developers. Adobe… pull your socks up.

    tom

    16 Jun 09 at 5:06 PM

  16. [...] I bring this up now because I recently had the pleasure of sharing this esoteric information with another developer who was facing similar issues. [...]

  17. This seems to be an issue for which there seems to be very little in the way of concise, usable documentation. In fact, this blog seems to be one of the few that contains a working solution!

    Having said that (and isn’t there always a but?), I’m running into the same problem as Curiousmind above in so far as that incorporating the information here produces a valid header (with nonce and using the fix for the password encryption in the adobe library), but an unauthorizable/unauthenticatable token.

    Other than writing a Java client (not exactly my forte but I can probably cobble something together) to the webservice so that I can capture a working header and compare it to the flex one, does anyone have any ideas on other routes to make some progress?

    Stuart

    9 Sep 09 at 1:23 PM

  18. Hi i not able to download sample code
    i need getUsernameTokenAsArray function…kindly help its urgent .
    or if possible somebody can send me a working solution at ankurgautam18@yahoo.co.in.

    Thanks in advance

    Ankur Gautam

    12 Sep 09 at 10:22 AM

  19. Nothing wrong with the download link as far as I can tell Ankur.

    A perfectly working solution has not been found by anybody just yet as you can tell from the other comments. I’ll have to test what Stuart writes, comparing the headers from a Java-originated call to those of a Flex-originated call to see why the Flex ones won’t validate.
    I’ll also have to test it against different versions of SOAP etc…

    Hope to have a new post on the subject soon that FINALLY contains a 100% working solution.

    Kind regards,
    Sven

    Sven Dens

    12 Sep 09 at 11:36 AM

  20. I’ve got it working to a point right now. We’ve used Axis to implement WSS on our server so I used this to knock together a java client then checked the password and nonce encoding were working correctly in the flex client (which they are). Basically in the end all I needed to do was to implement the timestamp token within the security element on the header:

    2009-09-14T10:53:01Z
    2009-09-14T10:58:01Z

    (This’ll be pretty badly formatted but it should be decipherable).

    The java client set the expiry time for the header as creation time + 5 mins, so I set the Flex entry to be the same (the created value is the same as the created entry within the UsernameToken).

    Having said that I supposed it’s possible that the requirement for the timestamp may be Axis and/or implementation specific, so it may not be the solution to anyone’s problems bar mine but it’s a starting point.

    There’s still one problem I haven’t overcome yet which is that if you call the same webservice operation a few times in a row after succeeding the first couple of times it permanently falls over with the error:

    soapenv:Server.userException – org.xml.sax.SAXParseException: The prefix “soapenv” for attribute “soapenv:mustUnderstand” is not bound.

    Once I get a solution for this I’ll post it here (I’m guessing it’s probably a server-side issue).

    Stuart

    14 Sep 09 at 6:21 PM

  21. Heh. You’d have thought I’d haven taken the whole parsing issue into account for posting XML, but no. The header (now with added entities) should read:

    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
    <wsse:UsernameToken>
    <wsse:Username>username</wsse:Username>
    <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">encoded pasword</wsse:Password>
    <wsse:Nonce>encoded nonce</wsse:Nonce>
    <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2009-09-14T10:53:01Z</wsu:Created>
    </wsse:UsernameToken>
    <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <wsu:Created>2009-09-14T10:53:01Z</wsu:Created>
    <wsu:Expires>2009-09-14T10:58:01Z</wsu:Expires>
    </wsu:Timestamp>
    </wsse:Security>

    Stuart

    14 Sep 09 at 6:32 PM

  22. OK, it seems to be sorted now. The cause was that I added SOAP-ENV:mustUnderstand=”1″ as an attribute under the wsse:Security element.

    Note that this is not present in Sven’s code so it was pretty much a problem of my own making. However, in the interests of disclosure…

    Adding this attribute seems, at least in the implementation I’m working with, to make the server pay attention to the wsu:Id attribute in the UsernameToken element along with the Created and Expires elements in the Timestamp (i.e. If you pass a different Id attribute value through the same browser session before the Timestamp has expired then it’s throwing an error).

    There’s two ways I’ve found to get around this:

    1. Exclude the mustUnderstand attribute – I’m not sure if this is desirable or not (and I also have no idea what this would do to any implementations that actually track sessions for a reason), but the WSS specs (http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0.pdf) state:

    “When a header includes a mustUnderstand=”true” attribute:

    The receiver MUST generate a SOAP fault if does not implement the WSS: SOAP Message Security specification corresponding to the namespace. Implementation means ability to interpret the schema as well as follow the required processing rules specified in WSS: SOAP Message Security.

    The receiver must generate a fault if unable to interpret or process security tokens contained in the header block according to the corresponding WSS: SOAP Message Security token profiles.

    Receivers MAY ignore elements or extensions within the element, based on local security policy.”

    2. Keep the Id attribute value consistent until the Timestamp expires (or in theory throughout the life of the session) – This would seem to be a slightly more official way of doing it.

    Either of these will make it work with the server implementation I’m using right now, although I’m storngly leaning towards the second option.

    Again, all the usual caveats apply regarding this being a fix for my specific implementation and relying on the server-side guys to have locked things down well enough from a security angle that I can’t actually implement an insecure solution. ;)

    Stuart

    15 Sep 09 at 1:26 PM

  23. In WSSEUsernameToken class in the getUsernameTokenAsArray function there is changed order of lines.
    Line:
    nonce = base64Encode(nonce);
    should be after the line:
    var password64:String = getBase64Digest(nonce, created, password);

    or it’s better to provide a new variable:
    var encodedNonce:String = base64Encode(nonce);
    var created:String = generateTimestamp(timestamp);
    var password64:String = getBase64Digest(nonce, created, password);
    var token:Array = new Array(username, password64, encodedNonce, created);

    Wojciech

    17 Sep 09 at 11:11 PM

  24. hi, i am trying to add digest authentication to client side, can some one let me how to write spring client for consuming spring web service which requires digest authentication. should i populate soap headers? if so, how to do that.
    i am using axis
    thanks

    web services new bee

    9 Oct 09 at 7:47 PM

  25. But after adding the security data in header how to fetch the same at the service side.. is at possible? because in headers object at service side m able to fetch all data except this security tag data..

    pramod

    13 Oct 09 at 2:30 PM

  26. Thanks for this wonderful post. I would like to add that the statement “Let me start off by saying that the Import Web Service (WSDL) wizard in Flex 3 doesn’t work as it should!” by Sven is completely true. But we have the, generated proxies working fine with Apache CXF hosted webservice using Security headers with a couple of tweaks.

    In order to implement the fix we need to understand the design using which the proxy files are generated.

    Here is how the proxies work:

    Let us suppose Demo being the service name.

    There is one DemoWebService.as file which is derived
    from interface IDemoService.as.
    There is another file called BaseDemoWebService.as which actually does the SOAP serialization and deserialization.

    The DemoWebService.as (ServiceImpl) delegates all the opreations to the BaseDemoWebService instance which is composed in it.

    Now in order to add the SOAP security headers we should add a method in the DemoWebService.as like setAuthentication(or whatever you like). This code is similar to what Sven did in his post. Clearing and adding the security headers.

    My custom method looks like:

    public function setAuthentication(userName:String, password:String):void
    {
    import be.svendens.util.SOAPHeaderUtil;
    import mx.rpc.soap.SOAPHeader;
    if (_baseService != null) {
    var customHeader:SOAPHeader = SOAPHeaderUtil.returnWSSEHeaderWithoutNonceAndTimestamp(userName, password);

    //customHeader.mustUnderstand = true;
    _baseService.clearHeaders();
    _baseService.addHeader(customHeader);
    }
    }

    The second and most important issue that needs to be fixed is in the BaseDemoWebService.as which is the core file. Here all the headers that we add are ignored as its everytime does the new for Header array.

    The simple fix is in BaseDemoService file.

    Find the line in method call

    soap = enc.encodeRequest(args,headers);

    and replace it with

    soap = enc.encodeRequest(args, super.headers);

    Just compile and verify the system will send your WSSE security headers to the webservice endpoint URI.

    All these fixes can be automated easily by writing a simple tool. We have created one tool which runs on
    the generated proxies and patches the proxies to support the WSSE headers exactly as mentioned above.

    Please contact us in case you need that tool for free :) or in case you have any other issue.

    Regards
    Gaurav
    gaurav_handa@nthdimenzion.com

    Gaurav

    24 Feb 10 at 7:41 AM

  27. Anyone know if the WSDL Wizard in Flash Builder 4 also has the problem? I’ve been having… issues.

    Clay

    25 Feb 10 at 11:18 PM

Leave a Reply