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.