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:This is because the format of the username determines the Credentials instance created. With the
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)
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.
5 comments:
definitively Great !
Dude this is one of the best examples I have found for this. Works perfectly!
thx so much! You speared me hours googling around.
Hey Kevin,
thanks a lot. It is important to mention that your solution applies to ntlm but not to ntlmv2. There is a other blogpost which explains how to fix this: http://devsac.blogspot.com/2010/10/supoprt-for-ntlmv2-with-apache.html
Greeting
Joe
Thanks - saved my bacon - the Domain\\username tip I mean. Cheers
Post a Comment