- Whitehorses - http://blog.whitehorses.nl -

Connecting to a NTLM Web Service using a Java Servlet

Tweet [1]

Connecting to a NTLM web service can be troublesome. This blog post shows the source code of a servlet which converts HTTP calls into HTTPS calls with NTLM. The servlet takes care of the NTLM handshake.

For a customer I needed to create a BPEL process which needed information from a web service that uses NTLM. There are many ways to solve this problem. My colleague used an Authenticator: JAX-WS web service proxy client and HTTP authentication [2]. I followed a different approach. I used the Apache HttpComponents libraries. The idea is to call a servlet as if it is the actual web service. It can be used from any application: e.g. BPEL process or soapUI. The application can be deployed on any servlet container, e.g. Apache Tomcat or OC4J.

NTLM Serlvlet

So, what is NTLM? NTLM is a Microsoft authentication protocol. Sometimes this protocol is used by applications delevoped using the Microsoft .Net Framework. The protocol involves a handshake in several steps. NTLM is a proprietary protocol, but it is fairly well documented. How to implement NTLM in a Java client? The answer is Apache HttpComponents. The latest version of Apache HttpComponents (version 4.1 GA) has full support for NTLMv1, NTLMv2, and NTLM2 Session authentication.

Since SOAP usually means sending XML over HTTP Post a Servlet can be used to handle these requests. There is no need to modify the contents of the HTTP Post Message. When handling a SOAP request, just remember to copy the HTTP Headers.

The servlet is part of a web application which contains only one class: NTLMProxyServlet. The libraries of Apache HttpComponents (version 4.1 GA) should be included in the war file. Do not forget to map an URL to the servlet in the deployment descriptor (web.xml).

The code of the doPost method of the class NTLMProxyServlet (implements HttpServlet) is shown below. Please replace the hardcoded values for host, domain, user, url, password:

protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    response.setCharacterEncoding("utf-8");
    PrintWriter out = response.getWriter();

    if(httpclient == null){
        httpclient = new DefaultHttpClient();
        httpclient.getAuthSchemes().register(
                "NTLM", new NTLMSchemeFactory());
        httpclient.getCredentialsProvider().setCredentials(
                new AuthScope("host", 443, "domain"),
                new NTCredentials("user", "password", "host", "domain"));
        httpclient.getParams().setParameter(
                CoreProtocolPNames.USE_EXPECT_CONTINUE, Boolean.FALSE);
        httpclient.getParams().setParameter(
                CoreProtocolPNames.PROTOCOL_VERSION,
                HttpVersion.HTTP_1_1);
        httpclient.getParams().setParameter(
                CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8");
    }

    HttpPost httppost = new HttpPost(
            "https://host/webservices/getdata.asmx");
    InputStream in = null;

    try {
        in = request.getInputStream();
        Writer writer = new StringWriter();
        char[] buffer = new char[1024];
        Reader reader = new BufferedReader(
                new InputStreamReader(in, "UTF-8"));
        int n;
        while ((n = reader.read(buffer)) != -1) {
            writer.write(buffer, 0, n);
        }
        httppost.setEntity(new StringEntity(writer.toString()));

        Enumeration headernames = request.getHeaderNames();
        while (headernames.hasMoreElements()) {
            String headerName = (String) headernames.nextElement();
            String headerValue = request.getHeader(headerName);
            httppost.setHeader(headerName, headerValue);
        }
        Header h1 = httppost.getFirstHeader(HTTP.CONTENT_LEN);
        if (h1 != null) {
            httppost.removeHeader(h1);
        }
        Header h2 = httppost.getFirstHeader(HTTP.TARGET_HOST);
        if (h2 != null) {
            httppost.removeHeader(h2);
        }
        httppost.setHeader(HTTP.TARGET_HOST, "host");

        HttpResponse httpresponse = httpclient.execute(
                new HttpHost("host", 443, "https"),
                httppost,
                new BasicHttpContext());
        HttpEntity output = httpresponse.getEntity();

        Header[] headers = httpresponse.getAllHeaders();

        for (Header header : headers) {
            if (!header.getName().equals(HTTP.CONTENT_LEN)) {
                response.setHeader(header.getName(), 
                        header.getValue());
            }
        }

        if (output != null) {
            out.write(EntityUtils.toString(output));
        }
    } catch (Exception e) {
        e.printStackTrace(out);
    } finally {
        in.close();
        out.flush();
        out.close();
    }
}
private DefaultHttpClient httpclient = null;

If the target server uses self-signed SSL certificates you might want to apply the code in Avoiding the “javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated” with HttpClient [3].

In order to use use the servlet make a local copy of the WSDL of the target web service and change the port address into the URL of the servlet, e.g.:

<wsdl:definitions>
  [...]
  <wsdl:service name="TargetWS">
    <wsdl:port name="TargetWSSoap12" binding="tns:TargetWSSoap12">
      <soap12:address location="http://localhost:8080/NTLMProxyApp/NTLMProxyServlet" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

Use this modified copy of the WSDL in your BPEL process of SoapUI project.