mikeobrien.net Curriculum Vitae Blog Labs
Monday, May 04, 2009

I came across a weird issue with proxy's generated by the Axis2 code generator from a WCF WSDL. Basically there is ambiguity between the String type defined in the Microsoft WSDL for primitive types which gets generated in the proxy and the java.lang.String. Supposedly this issue has been fixed but I still had an issue with the code generator plugin that installs with the Eclipse Ganymede plugin manager. Anyway's, to work around it I just navigated to the error:

image

And specified the FQ name for the Java string type and it worked fine:

image

Monday, May 04, 2009 12:32:49 AM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Saturday, April 25, 2009

Kevin Rohling is my hero; he created a library to add the WSS UsernameToken headers in SilverLight 2.0! Check it out.

Saturday, April 25, 2009 7:15:03 AM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback

I have run into a couple of instances where I've had to manually add a WSS UsernameToken to consume a WCF service with the basicHttpBinding and a UsernameToken. The client could not understand the WSS timestamp returned by WCF (Notably ColdFusion/Axis1.2 w/ no wss4j). If you are trying to achieve improbability and want to just turn this off there isn't a way to disable the timestamp in the basicHttpBinding or wsHttpBinding through configuration but you can use the customBinding. To emulate basicHttpBinding with transport security and message credentials but with the timestamp off you can define the custom binding as follows:

...
<bindings>
  <customBinding>
    <binding name="MyBinding">
      <security authenticationMode="UserNameOverTransport" includeTimestamp="false" />
      <textMessageEncoding messageVersion="Soap11" />
      <httpsTransport />
    </binding>
  </customBinding>
</bindings>
...
Saturday, April 25, 2009 3:28:30 AM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback

The following demonstrates how to consume a web service that requires a WSS UsernameToke with ColdFusion:

<html>
<head><title>My Web Services</title></head>
<body>
<h2>My Web Services</h2>

<cfscript>

service = createObject("webservice", "https://services.nsa.gov/NOCList.svc?wsdl");

AddCredentials(service, "username", "p@$$w0rd");

result = service.DoSomething();

writeoutput("Result=" & result);

function AddCredentials(service, username, password)
{
      wssNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
      header = createObject("java", "org.apache.axis.message.SOAPHeaderElement");
      header.init(wssNamespace, "wsse:Security");
      header.addChildElement("wsse:UsernameToken");
      header.getFirstChild().addChildElement("wsse:Username").setValue(username);
      header.getFirstChild().addChildElement("wsse:Password").setValue(password);
      header.setMustUnderstand(1);
      service.setHeader(header);
}

</cfscript>

</body>
</html>
Saturday, April 25, 2009 3:09:03 AM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Friday, April 24, 2009

The following are a couple of things I've discovered about web service namespaces while working on interoperability:

1) If your not going to use a URL for your namespace you need to prepend your namespace with a uri scheme (IE: "myscheme:MyNamespace") to signify that it is absolute. The urn scheme is an appropriate choice but this isn't required as the namespace must simply be a URI. The Microsoft stacks don't seem to mind if the scheme is not present but the Apache Xml Security library is finicky about this (Thinking its a relative namespace if its not there), you will see the following error on the client side if you don't:

org.apache.xml.security.c14n.CanonicalizationException: Element ns1:doSomething has a relative namespace: xmlns:ns1="MyNamespace"

2) Use the same namespace for your binding namespace, behavior namespace, and all service and data contract namespaces. WCF will break out WSDL artifacts from different namespaces into different files and some proxy generators don't like this (Notably ColdFusion).

Friday, April 24, 2009 8:09:06 PM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Wednesday, April 22, 2009

So here is the scenario on the service side: WCF, basicHttpBinding, UsernameToken Profile (No signing or encryption). On the client side we are working in Eclipse Ganymede, JRE 6, JDK 1.6 Update 13. The following steps outline how to consume the WCF service.

1) Download wss4j-1.5.7.jar from here (Pick a mirror) and save it to your Eclipse plugins folder (Or where ever you like). More information about wss4j can be found here.

2) Download xml-security-bin-1_4_2.zip from here and unzip it into temporary folder. Copy the contents of the libs folder into a subfolder under the Eclipse plugins folder called org.apache.xml.security_1.4 (Or where ever you like). More information about Apache XML Security can be found here.

3) If you haven't already done so create a new Java project and add references to the wss4j-1.5.7.jar and org.apache.xml.security_1.4\xmlsec-1.4.2.jar file.

4) If you haven't already, install the Web Services Tools (WST). Go to "Help|Software Updates" and select the "Available Software" tab. Expand the "Web Tools (WTP) Update Site" node and check the "Web Tools Platform (WTP) x.x.x" node. Then click the "Install" button. Opt to restart the IDE after the install.

image

5) Next right click your project and select "New|Other". Scroll down and expand the "Web Services" node and select "Web Service Client" and click Next:

image

6) Enter the url to the WSDL and make sure the "Client type" is set to "Java Proxy". Next under "Configuration" the "Web service runtime" should be "Apache Axis" and the "Client project" should be your project, if not click the property link and make the appropriate modifications. Move the slider all the way down until it is set to "Develop client".

image

7) Create a new class called Credentials and paste in the following code:

import javax.security.auth.callback.Callback;
import org.apache.axis.client.Stub;
import org.apache.ws.security.WSPasswordCallback;
import org.apache.ws.security.handler.WSHandlerConstants;
import javax.security.auth.callback.CallbackHandler;

public class Credentials
{
    static class PasswordCallback implements CallbackHandler {
        private String password;
        public PasswordCallback(String password)
        { this.password = password; }
        public void handle(Callback[] callbacks) 
        {((WSPasswordCallback)callbacks[0]).setPassword(this.password); }
    }
    
    public static void Add(Stub stub, String username, String password)
    {
        stub._setProperty(WSHandlerConstants.USER, username);
        stub._setProperty(WSHandlerConstants.PW_CALLBACK_REF, 
                            new PasswordCallback(password));
    }
}

8) Create a new file called ServiceConfig.wsdd and paste in the following xml:

<deployment xmlns="http://xml.apache.org/axis/wsdd/" 
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
  <transport name="http" 
             pivot="java:org.apache.axis.transport.http.HTTPSender"/>
  <globalConfiguration >
    <requestFlow >
      <handler type="java:org.apache.ws.axis.security.WSDoAllSender" >
        <parameter name="action" value="UsernameToken"/>
        <parameter name="passwordType" value="PasswordText"/>
      </handler>
    </requestFlow>
    <responseFlow>
      <handler type="java:org.apache.ws.axis.security.WSDoAllReceiver">
        <parameter name="action" value="Timestamp"/>
      </handler>
    </responseFlow>
  </globalConfiguration >
</deployment>

9) If one is not already created then create a class called Startup and paste in the following code. Note, you must specify the bracketed portions as they will be named differently depending on the service and its WSDL.

import <ServiceNamespace>.*;
import org.apache.axis.*;
import java.rmi.RemoteException;
import org.apache.axis.client.Stub;
import javax.xml.rpc.ServiceException;
import org.apache.ws.security.handler.*;
import org.apache.axis.configuration.FileProvider;

public class Startup 
{
    public static void main(String[] args) throws RemoteException, ServiceException 
    {
        EngineConfiguration config = new FileProvider("ServiceConfig.wsdd");
        <ServiceName>ServiceLocator locator = new <ServiceName>ServiceLocator(config);
        <ServiceInterface> service = locator.get<ServiceStub>();
        
        Credentials.Add((Stub)service, "Username", "P@$$w0rd");

        <ResultType> result = service.<Method>();
        System.out.println(result);
    }
}

 

At this point you should be able to successfully consume the target service.

Wednesday, April 22, 2009 11:22:28 PM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Friday, April 17, 2009

I have been struggling all week trying to get a client created with the NetBeans 6.5.1 IDE to communicate with a WCF service using the wsHttpBinding. The symptoms were that it would connect and just hang when I made the soap call. I took a look at the message that was being sent across the wire and I noticed that JAX-WS was not sending the WS-Addressing headers. So after much searching and hitting the Metro message board and gave up and moved onto something else. While I was working on that something else I happened to look at the "port" factory method on the proxy that's generated by wsimport and noticed that you could pass multiple "WebServiceFeature"s, one of which was one for WS-Addressing! Passed that in and voila, it worked! Here is an example:

try { // Call Web Service Operation
    yada.SystemService service = new yada.SystemService();
    yada.Sys port = service.getYadaSystem(new AddressingFeature(true, true));
    // TODO process result here
    yada.VersionInfo result = port.getVersion();
    System.out.println("Result = "+result);
} catch (Exception ex) {
    System.out.println(ex.getMessage());
}

You can read more about this class here. I have no idea why wsimport doesent just bake this into the proxy as the policy in the WSDL clearly states that it uses WS-Addressing by way of the UsingAddressing element. Strange...

Friday, April 17, 2009 10:45:16 PM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Tuesday, April 07, 2009

Adding a WSS UsernameToken with the native PHP SoapClient is pretty straight forward (Mind you, this is just the plain text credentials so you should use transport security). Here is the usage:

$wsdl = "https://services.yada.net/Yada.svc?wsdl";

// Or 'soap_version' => SOAP_1_1 if your using SOAP 1.1
$options = array(
    'location' => 'https://services.yada.net/Yada.svc',
    'soap_version' => SOAP_1_2);

$client = new SoapClient($wsdl, $options);

// Add the WSS username token headers
AddWSSUsernameToken($client, 'tony', 'clifton');

try
{
    $client->GetVersion();

    echo str_replace('>', '&gt;<br/>', 
        str_replace('<', '&lt;', 
            str_replace('&', '&amp;', 
                $client->__getLastResponse())));
}
catch(Exception $e)
{
    echo $e;
}

Here is the implementation:

function AddWSSUsernameToken($client, $username, $password)
{
    $wssNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
    
    $username = new SoapVar($username, 
                            XSD_STRING, 
                            null, null, 
                            'Username', 
                            $wssNamespace);
                            
    $password = new SoapVar($password, 
                            XSD_STRING, 
                            null, null, 
                            'Password', 
                            $wssNamespace);
    
    $usernameToken = new SoapVar(array($username, $password), 
                                    SOAP_ENC_OBJECT, 
                                    null, null, 'UsernameToken', 
                                    $wssNamespace);
                            
    $usernameToken = new SoapVar(array($usernameToken), 
                            SOAP_ENC_OBJECT, 
                            null, null, null, 
                            $wssNamespace);
    
    $wssUsernameTokenHeader = new SoapHeader($wssNamespace, 'Security', $usernameToken);
    
    $client->__setSoapHeaders($wssUsernameTokenHeader); 
}
Tuesday, April 07, 2009 1:26:04 PM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Friday, April 03, 2009

As I mentioned earlier, I'm testing our SOAP API on a few different platforms and am using PocketSOAP as a COM solution for scripting languages (Like VBScript). It supports SOAP 1.2 but it doesn't support the WS-* standards out of the box. If you simply need to use the UsernameToken part of the WSS spec and you are sending your password as plain text (Over SSL right?) you can use the following VBScript method to add the headers:

Sub AddWSSUsernameToken(envelope, username, password)

    Dim WSSNamespace, Security, UsernameToken, Serializer
    WSSNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"

    Security = envelope.Headers.Create("Security", "", WSSNamespace)
    UsernameToken = Security.Nodes.Create("UsernameToken", "", WSSNamespace)
    ' NOTE: If your using the SOAP 1.1 version of the envelope you
    '       need to add the following line:
    ' UsernameToken.root = true
    UsernameToken.Nodes.Create("Username", username, WSSNamespace)
    UsernameToken.Nodes.Create("Password", password, WSSNamespace)

    Serializer = envelope.SerializerFactory
    Serializer.understoodHeader(WSSNamespace, "Security")

End Sub

As noted if your using the SOAP v1.1 envelope you need to include the line where the root property is set for the UsernameToken node. Here is the usage:

Dim envelope
Set envelope = CreateObject("pocketSOAP.Envelope.12")

envelope.SetMethod "SomeMethod", "SomeNamespace"

AddWSSUsernameToken envelope, "tony", "clifton"

 

UPDATE: Here is the implementation in JScript:

function AddWSSUsernameToken(envelope, username, password)
{
    var wssNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";

    var security = envelope.Headers.Create("Security", "", wssNamespace);
    var usernameToken = security.Nodes.Create("UsernameToken", "", wssNamespace);
    // NOTE: If your using the SOAP 1.1 version of the envelope you 
    // need to add the following line: 
    // usernameToken.root = true;
    usernameToken.Nodes.Create("Username", username, wssNamespace);
    usernameToken.Nodes.Create("Password", password, wssNamespace);

    var serializer = envelope.SerializerFactory;
    serializer.understoodHeader(wssNamespace, "Security");
}
Friday, April 03, 2009 11:57:04 PM (GMT Daylight Time, UTC+01:00)  #   |  Comments [0]  |  Trackback
Creative Commons License