Showing posts with label axis. Show all posts
Showing posts with label axis. Show all posts

Tuesday, March 08, 2011

NTLM from an Axis (SOAP) service client - in 3 steps

Keywords:
NTLM authentication Negotiate Apache axis SOAP IIS Windows Integrated Authentication CommonsHTTPSender NTCredentials

Problem:
Authenticating a service request with BASIC authentication is (relatively) straightforward:

import java.net.URL;
import org.apache.axis.client.Stub;
import com.example.service.Example;
import com.example.service.ExampleServiceLocator;
import com.example.service.ExampleRequest;
import com.example.service.ExampleResponse;

// get access to the web service
ExampleServiceLocator locator = new ExampleServiceLocator();
String serviceURL = "http://server/application/services/example";
Example example = locator.getexample(new URL(serviceURL));
// set credentials
((Stub)example).setUsername("myusername");
((Stub)example).setPassword("mypassword");


// setup request
ExampleRequest request = new ExampleRequest();
request.setProperty("SomeProperty");

ExampleResponse response = example.example(request);


What if the (SOAP) service being called required NTLM authentication (e.g. the service is running in IIS and security is set as "Windows Integrated Authentication")?

Solution:
The following three steps are assuming Axis 1.x. The Apache Axis Client Tips and Tricks is a good reference, in particular for step 2, but also for other "tips".

Step 1: Add Apache commons-httpclient (3.1) and commons-codec libraries


Note you must add the commons httpclient jar file and not the (latest/refactored) apache httpclient to the project - or you will get ClassNotFound exceptions.

Step 2: Define custom client-config with CommonsHTTPSender


It's mentioned in the "Tips and Tricks" article mentioned above, but you can either: (a) define a custom client-config.wsdd file in the classpath before axis.jar; (b) edit the generated ...ServiceLocator.java generated class and make it override getEngine...; or (c) at runtime simply feed the customised config XML to your ...ServiceLocator object.

I prefer the latter - for example, define a static method with the config XML as a string:

protected static org.apache.axis.EngineConfiguration getEngineConfiguration() {
java.lang.StringBuffer sb = new java.lang.StringBuffer();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
sb.append("<deployment name=\"defaultClientConfig\"\r\n");
sb.append("xmlns=\"http://xml.apache.org/axis/wsdd/\"\r\n");
sb.append("xmlns:java=\"http://xml.apache.org/axis/wsdd/providers/java\">\r\n");
// sb.append("<transport name=\"http\" pivot=\"java:org.apache.axis.transport.http.HTTPSender\" />\r\n");
sb.append("<transport name=\"http\" pivot=\"java:org.apache.axis.transport.http.CommonsHTTPSender\" />\r\n");
sb.append("<transport name=\"local\" pivot=\"java:org.apache.axis.transport.local.LocalSender\" />\r\n");
sb.append("<transport name=\"java\" pivot=\"java:org.apache.axis.transport.java.JavaSender\" />\r\n");
sb.append("</deployment>\r\n");
org.apache.axis.configuration.XMLStringProvider config =
new org.apache.axis.configuration.XMLStringProvider(sb.toString());
return config;
}


Then the call to the locator would become:

// get access to the web service
ExampleServiceLocator locator = new ExampleServiceLocator(getEngineConfiguration());


Step 3: Set the username as DOMAIN\username


Set the username as you did with BASIC authentication but you must ensure is set in the form DOMAIN\username (keeping in mind that if expressing this in java code - as a string - or as a property value in a properties file this would be set as "DOMAIN\\username" - \\ being the escape sequence for \):

((Stub)example).setUsername("MY_NT_DOMAIN\\myusername");
((Stub)example).setPassword("mypassword");


With the above 3 steps covered you're using NTLM.

Notes:
Avoid setting the system property -Djava.ext.dirs as the above relies on the sunjce_provider.jar library which is in JRE_HOME\lib\ext by default. Ext-path problems may give you errors such as:
"Cannot find any provider supporting DES/ECB/NoPadding"


Failing to set the username in the form DOMAIN\username will result in the error:
org.apache.commons.httpclient.auth.InvalidCredentialsException: 

Credentials cannot be used for NTLM authentication:
org.apache.commons.httpclient.UsernamePasswordCredentials
at org.apache.commons.httpclient.auth.NTLMScheme.authenticate(NTLMScheme.java:332)
at org.apache.commons.httpclient.HttpMethodDirector.authenticateHost(HttpMethodDirector.java:282)
at org.apache.commons.httpclient.HttpMethodDirector.authenticate(HttpMethodDirector.java:234)
at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:170)
at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397)
at org.apache.axis.transport.http.CommonsHTTPSender.invoke(CommonsHTTPSender.java:186)
This is because the format of the username determines the Credentials instance created. With the DOMAIN\... prefix on the username you get an instance of org.apache.commons.httpclient.NTCredentials rather than org.apache.commons.httpclient.UsernamePasswordCredentials - which as the message explains can't be used for NTLM.

Thursday, June 03, 2010

Stop AXIS output of anonymous complex types in xsi:type

Keywords:
apache axis xsi:type anonymous complex type .NET "No type definition found for the type referenced by the attribute 'xsi:type'"

Problem:
The schema definition for a response element may define the complex type "inline" rather than by reference. This is known as an "anonymous type". Eg:
<xs:element name="MyResponse">
    <xs:annotation>
        <xs:documentation>This is the root element of the Response.</xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element name="PropertyOne" type="xs:string" minOccurs="0"/>
            <xs:element name="PropertyTwo" type="xs:string" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

When axis generates the SOAP response (with the default settings) this includes xsi:type attributes. Eg:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
     <MyResponse xsi:type="ns1:MyResponse" xmlns="http://example.com/service" xmlns:ns1="http://example.com/service">
        ...

This is accepted by AXIS-generated client code but in .NET (and other schema-validating tools such as XMLSpy) you'll get an error along the lines of
No type definition found for the type referenced by the attribute 'xsi:type'='ns1:MyResponse' of element <MyResponse>

The error inidicates validation is probably doing the right thing with the xsi:type attribute and checking it - there is actually no type with this name, it's anonymous. Can the behavour in AXIS be changed?

Solution:
Thankfully there is a configuration setting in AXIS to stop this output - sendXsiTypes, which is true by default:
<parameter name="sendXsiTypes" value="false"/>

There are a number of ways to get this setting in there. I'd recommend creating a wsdd deploy file for just the globalConfiguration and deploy this to the AXIS AdminServlet as you would do with the deploy.wsdd definitions for the other service(s).
So an example globalConfig-deploy.wsdd:
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
    <globalConfiguration>
        ...
        <parameter name="sendXsiTypes" value="false" />
        ...
    </globalConfiguration>
</deployment>

You'll find the MyApp/WEB-INF/server-config.wsdd will then be updated and the SOAP excludes this attribute as advertised:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
     <MyResponse xmlns="http://example.com/service">
        ...


The alternative to this (if you do want the xsi:type attributes) is to define explicit types for your root response elements. Eg:
<xs:element name="MyResponse" type="MyResponseType">
    <xs:annotation>
        <xs:documentation>This is the root element of the Response.</xs:documentation>
    </xs:annotation>
</xs:element>
<xs:complexType name="MyResponseType">
    <xs:annotation>
        <xs:documentation>This is the type for the root element of the Response.</xs:documentation>
    </xs:annotation>
    <xs:sequence>
        <xs:element name="PropertyOne" type="xs:string" minOccurs="0"/>
        <xs:element name="PropertyTwo" type="xs:string" minOccurs="0"/>
    </xs:sequence>
</xs:complexType>

The XML from AXIS will then include xsi:type but the value will be correct - referencing a type that does exist. Eg:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
     <MyResponse xsi:type="ns1:MyResponseType" xmlns="http://example.com/service" xmlns:ns1="http://example.com/service">
        ...