I’m trying to use the Apache/Jakarta HttpClient 4.1.1 to connect to an arbitrary web page using the given credentials. To test this, I have a minimal install of IIS 7.5 on my dev machine running where only one authentication mode is active at a time. Basic authentication works fine, but Digest and NTLM return 401 error messages whenever I try to log in. Here is my code:
DefaultHttpClient httpclient = new DefaultHttpClient(); HttpContext localContext = new BasicHttpContext(); HttpGet httpget = new HttpGet("http://localhost/"); CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials(AuthScope.ANY, new NTCredentials("user", "password", "", "localhost")); if (!new File(System.getenv("windir") + "\krb5.ini").exists()) { List<String> authtypes = new ArrayList<String>(); authtypes.add(AuthPolicy.NTLM); authtypes.add(AuthPolicy.DIGEST); authtypes.add(AuthPolicy.BASIC); httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authtypes); httpclient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, authtypes); } localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider); HttpResponse response = httpclient.execute(httpget, localContext); System.out.println("Response code: " + response.getStatusLine());
The one thing I’ve noticed in Fiddler is that the hashes sent by Firefox versus by HttpClient are different, making me think that maybe IIS 7.5 is expecting stronger hashing than HttpClient provides? Any ideas? It’d be great if I could verify that this would work with NTLM. Digest would be nice too, but I can live without that if necessary.
Advertisement
Answer
I am not an expert on the subject but during the NTLM authentication using http components I have seen that the client needs 3 attempts in order to connect to an NTML endpoint in my case. It is kinda described here for Spnego but it is a bit different for the NTLM authentication.
For NTLM in the first attempt client will make a request with Target auth state: UNCHALLENGED
and Web server returns HTTP 401 status and a header: WWW-Authenticate: NTLM
Client will check for the configured Authentication schemes, NTLM should be configured in client code.
Second attempt, client will make a request with Target auth state: CHALLENGED
, and will send an authorization header with a token encoded in base64 format: Authorization: NTLM TlRMTVNTUAABAAAAAYIIogAAAAAoAAAAAAAAACgAAAAFASgKAAAADw==
Server again returns HTTP 401 status but the header: WWW-Authenticate: NTLM
now is populated with encoded information.
3rd Attempt Client will use the information from WWW-Authenticate: NTLM
header and will make the final request with Target auth state: HANDSHAKE
and an authorisation header Authorization: NTLM
which contains more information for the server.
In my case I receive an HTTP/1.1 200 OK
after that.
In order to avoid all this in every request documentation at chapter 4.7.1 states that the same execution token must be used for logically related requests. For me it did not worked.
My code:
I initialize the client once in a @PostConstruct
method of an EJB
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(18); cm.setDefaultMaxPerRoute(6); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(30000) .setConnectTimeout(30000) .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM)) .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC)) .build(); CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new NTCredentials(userName, password, hostName, domainName)); // Finally we instantiate the client. Client is a thread safe object and can be used by several threads at the same time. // Client can be used for several request. The life span of the client must be equal to the life span of this EJB. this.httpclient = HttpClients.custom() .setConnectionManager(cm) .setDefaultCredentialsProvider(credentialsProvider) .setDefaultRequestConfig(requestConfig) .build();
Use the same client instance in every request:
HttpPost httppost = new HttpPost(endPoint.trim()); // HttpClientContext is not thread safe, one per request must be created. HttpClientContext context = HttpClientContext.create(); response = this.httpclient.execute(httppost, context);
Deallocate the resources and return the connection back to connection manager, at the @PreDestroy method of my EJB:
this.httpclient.close();