Does JMeter fully support NTLM Authentication?

2

I am struggling to make JMeter to work with NTLM authentication. At the beginning I was provided with an URL and credential. When I tested the credential in Firefox and in Chrome I received the authentication popup and upon providing the credential I was authenticated. So I had created a Test Plan with the following configuration:

  • HTTP Authorization Manager
  • HTTP Request Default
  • HTTP Request

I didn't know about the requirement of the Domain for the NTLM Authentication Schema. So eventually the JMeter was failing to authenticate and was returning HTTP 401 error.

Then I tried Bad boy to record the test script. When I had entered the URL in Bad boy I received Windows Authentication Popup and the given credential didn't work in Bad boy.

So I tried the IE and received same Windows Authentication Popup and the credential didn't worked. I asked for the domain and upon providing that domain in IE as Domain\Username I was successful to authenticate that user.

I tried the same with the JMeter and have provided the Domain in the HTTP Authorization Manager. Unfortunately it didn't work in JMeter. Following is my Test Plan. I have replaced the original URL, domain and the credential with aliases.

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="2.8" jmeter="2.13 r1665067">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
      <stringProp name="TestPlan.comments"></stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
      <stringProp name="TestPlan.user_define_classpath"></stringProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
          <boolProp name="LoopController.continue_forever">false</boolProp>
          <stringProp name="LoopController.loops">1</stringProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">1</stringProp>
        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
        <longProp name="ThreadGroup.start_time">1429694411000</longProp>
        <longProp name="ThreadGroup.end_time">1429694411000</longProp>
        <boolProp name="ThreadGroup.scheduler">false</boolProp>
        <stringProp name="ThreadGroup.duration"></stringProp>
        <stringProp name="ThreadGroup.delay"></stringProp>
      </ThreadGroup>
      <hashTree>
        <AuthManager guiclass="AuthPanel" testclass="AuthManager" testname="HTTP Authorization Manager" enabled="true">
          <collectionProp name="AuthManager.auth_list">
            <elementProp name="" elementType="Authorization">
              <stringProp name="Authorization.url">https://my_domain</stringProp>
              <stringProp name="Authorization.username">username</stringProp>
              <stringProp name="Authorization.password">password</stringProp>
              <stringProp name="Authorization.domain">NTLM_DOMAIN</stringProp>
              <stringProp name="Authorization.realm"></stringProp>
            </elementProp>
          </collectionProp>
        </AuthManager>
        <hashTree/>
        <ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
            <collectionProp name="Arguments.arguments"/>
          </elementProp>
          <stringProp name="HTTPSampler.domain">my_domain</stringProp>
          <stringProp name="HTTPSampler.port"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
          <stringProp name="HTTPSampler.path"></stringProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <stringProp name="HTTPSampler.concurrentPool">4</stringProp>
        </ConfigTestElement>
        <hashTree/>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
            <collectionProp name="Arguments.arguments"/>
          </elementProp>
          <stringProp name="HTTPSampler.domain"></stringProp>
          <stringProp name="HTTPSampler.port"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
          <stringProp name="HTTPSampler.response_timeout"></stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
          <stringProp name="HTTPSampler.path">/</stringProp>
          <stringProp name="HTTPSampler.method">GET</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">true</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
          <boolProp name="HTTPSampler.monitor">false</boolProp>
          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
        </HTTPSamplerProxy>
        <hashTree/>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

I was getting frustrated to make JMeter to work. I have tried both the implementation of HttpClient3.1 & 4; none of them have worked. Then I have downloaded the Source Code to see if there is anything I can dig up with.

These two classes deals with the HTTP implementation of JMeter:

  • org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl (for HttpClient4)
  • org.apache.jmeter.protocol.http.sampler.HTTPHC3Impl (for HttpClient3.1)

I didn't find anything wrong.

I tried the authentication via Java code. Following is the implementation of the authentication using common-httpclient-3.1:

import java.io.IOException;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;

public class NTLMAuthenticationHttpClient {

    public static void main(String[] args) throws HttpException, IOException {
        HttpClient client = new HttpClient();
        Credentials credentials = new NTCredentials("username", "password", "", "NTLM_DOMAIN");
        HttpState state = client.getState();
        state.setCredentials(AuthScope.ANY, credentials);

        String domain = "my_domain";
        String protocol = "https";

        HttpMethod method = new GetMethod(protocol + "://" + domain);
        method.setDoAuthentication(true);
        int status = client.executeMethod(method);
        System.out.println(status);
    }
}

This piece of code once or twice had returned HTTP 401 and most of the time I got HTTP 200.

Following is the implementation using httpclient-4.4.1:

import java.io.IOException;

import jcifs.ntlmssp.NtlmFlags;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.util.Base64;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.impl.auth.BasicSchemeFactory;
import org.apache.http.impl.auth.DigestSchemeFactory;
import org.apache.http.impl.auth.KerberosSchemeFactory;
import org.apache.http.impl.auth.NTLMEngine;
import org.apache.http.impl.auth.NTLMEngineException;
import org.apache.http.impl.auth.NTLMScheme;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
import org.apache.http.protocol.HttpContext;

public class NTLMAuthenticationHttpComponent {

    public static void main(String[] args) throws ClientProtocolException,
            IOException {
        Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder
                .<AuthSchemeProvider> create()
                .register(AuthSchemes.NTLM, new AuthSchemeProvider() {

                    public AuthScheme create(HttpContext context) {
                        return new NTLMScheme(new JCIFSEngine());
                    }
                }).register(AuthSchemes.BASIC, new BasicSchemeFactory())
                .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
                .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
                .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
                .build();

        String domain = "my_domain";
        String protocol = "https";

        HttpHost targetHost = new HttpHost(domain, 443, protocol);

        CredentialsProvider credentialsProvider = new SystemDefaultCredentialsProvider();
        credentialsProvider.setCredentials(
                new AuthScope(targetHost.getHostName(), targetHost.getPort()),
                new NTCredentials("username", "password", null, "NTLM_DOMAIN"));

        CloseableHttpClient client = HttpClients.custom()
                .setDefaultAuthSchemeRegistry(authSchemeRegistry)
                .setDefaultCredentialsProvider(credentialsProvider).build();

        HttpGet httpget = new HttpGet(protocol + "//" + domain);

        HttpResponse response = client.execute(httpget);
        System.out.println(response.getStatusLine().getStatusCode());
    }

    private static final class JCIFSEngine implements NTLMEngine {

        private static final int TYPE_1_FLAGS = 
                NtlmFlags.NTLMSSP_NEGOTIATE_56 | 
                NtlmFlags.NTLMSSP_NEGOTIATE_128 | 
                NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2 | 
                NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | 
                NtlmFlags.NTLMSSP_REQUEST_TARGET;

        public String generateType1Msg(final String domain, final String workstation)
                throws NTLMEngineException {
            final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS, domain, workstation);
            return Base64.encode(type1Message.toByteArray());
        }

        public String generateType3Msg(final String username, final String password,
                final String domain, final String workstation, final String challenge)
                throws NTLMEngineException {
            Type2Message type2Message;
            try {
                type2Message = new Type2Message(Base64.decode(challenge));
            } catch (final IOException exception) {
                throw new NTLMEngineException("Invalid NTLM type 2 message", exception);
            }
            final int type2Flags = type2Message.getFlags();
            final int type3Flags = type2Flags
                    & (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
            final Type3Message type3Message = new Type3Message(type2Message, password, domain,
                    username, workstation, type3Flags);
            return Base64.encode(type3Message.toByteArray());
        }
    }
}

This is always returning Http 401 Unauthorized error. This code was taked from the HTTP Components site, which is using JCIFS.

I am unable to find any cause for the Unauthorization. Does JMeter or the HTTPClient supports NTLM Authentication fully? I am having some doubt after reading this note from the above site:

NTLM is a proprietary authentication scheme developed by Microsoft and optimized for Windows operating system.

Until year 2008 there was no official, publicly available, complete documentation of the protocol. Unofficial 3rd party protocol descriptions existed as a result of reverse-engineering efforts. It was not really known whether the protocol based on the reverse-engineering were complete or even correct.

Microsoft published MS-NLMP and MS-NTHT specifications in February 2008 as a part of its Interoperability Principles initiative.

HttpClient as of version 4.1 initially supported NTLMv1, NTLMv2, and NTLM2SessionResponse authentication protocols, based on the reverse engineering approach. As of version 4.2.3, HttpClient now supports a more correct implementation, based in large part on Microsoft's own specifications. This is expected to correct a number of problems, especially since Microsoft (as of Windows Server 2008 R2) began using a new implementation of its protocols. This new Microsoft implementation has led to authentication failures in some cases from some of the older reverse-engineered client implementations of NTLM.

The new HttpClient NTLM implementation is known to have been tried successfully against at least the following systems:

  • Windows Server 2000 and Server 2003 systems, configured to use LM and NTLMv1 authentication
  • Windows Server 2003 systems, configured to use NTLMv2 authentication
  • Windows Server 2008 R2 systems, configured to use NTLM2SessionResponse authentication

If the current HttpClient NTLM implementation should prove problematic in your environment, we'd definitely like to hear about it.

In browser when I am browing the URL which I have used for the authentication it is asking for the credential then it is navigating to the home page. There is another intermediate page which returns HTTP 302 for the redirection.

Any suggestion will be a big help for me.

Update:

In JMeter following is the result I am getting with the Response Header:

Thread Name: Thread Group 1-1
Sample Start: 2015-04-26 14:26:39 IST
Load time: 3837
Connect Time: 2716
Latency: 3837
Size in bytes: 940
Headers size in bytes: 940
Body size in bytes: 0
Sample Count: 1
Error Count: 1
Response code: 401
Response message: Unauthorized

Response headers:
HTTP/1.1 401 Unauthorized
Cache-Control: max-age=0
Content-Type: text/plain
Date: Sun, 26 Apr 2015 08:56:42 GMT
Expires: Sun, 26 Apr 2015 08:56:43 GMT
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=0D39812DAECAED077E7A9001864874A9.schbapxu1044_SEP; Expires=Sun, 26-Apr-2015 16:56:42 GMT; Path=/; Secure; HttpOnly
Set-Cookie: dtCookie=2929007D72E613D13BF40F8241EC4B9F|X2RlZmF1bHR8MQ; Path=/; Domain=.my_domain_part2
Set-Cookie: AWSELB=C5C5577906943F772312365AC913FBE510FFA9A080FC6FD7778CB3F66B01593D16E110291976D6D7D50FBFB1DB51745A84041319D726B0F898FAE4520DC36E25BB9AE95FBCB14D902FBC9B5903E8BCB6E32414584F;PATH=/;EXPIRES=Sun, 26-Apr-2015 16:56:42 GMT;SECURE;HTTPONLY
Vary: Accept-Encoding
Via: 1.1 my_domain_part1.my_domain_part2
WWW-Authenticate: NTLM
X-Content-Type-Options: nosniff
X-dynaTrace-JS-Agent: true
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1
Content-Length: 0
Connection: keep-alive


HTTPSampleResult fields:
ContentType: text/plain
DataEncoding: null
java
jmeter
apache-httpclient-4.x
ntlm
apache-commons-httpclient
asked on Stack Overflow Apr 25, 2015 by Tapas Bose • edited Apr 26, 2015 by Tapas Bose

2 Answers

0

It does given you provide username, password and domain in HTTP Authorization Manager

See Windows Authentication with Apache JMeter guide for detailed explanation and configuration details.

answered on Stack Overflow Apr 26, 2015 by Dmitri T
-3

No one fully supports NTLM fully except Microsoft due to its proprietary nature.

answered on Stack Overflow Apr 26, 2015 by Michael-O

User contributions licensed under CC BY-SA 3.0