JMeter是否完全支持NTLM身份验证?

时间:2015-04-25 13:02:03

标签: java jmeter apache-httpclient-4.x ntlm apache-commons-httpclient

我正在努力让JMeter使用NTLM身份验证。一开始,我获得了一个URL和凭证。当我在Firefox和Chrome中测试凭证时,我收到了身份验证弹出窗口,并在提供凭据后进行了身份验证。所以我用以下配置创建了一个测试计划:

  • HTTP授权管理器
  • HTTP请求默认
  • HTTP请求

我不知道域名对NTLM身份验证架构的要求。因此,最终JMeter无法进行身份验证并返回HTTP 401错误。

然后我尝试了Bad boy来记录测试脚本。当我在Bad boy中输入URL时,我收到了Windows身份验证弹出窗口,并且给定的凭据在Bad boy中无效。

所以我尝试了IE并收到了相同的Windows身份验证弹出窗口,但凭据并没有起作用。我要求域名,并在IE中提供域名作为域\用户名,我成功验证了该用户。

我尝试使用JMeter,并在HTTP授权管理器中提供了域。不幸的是,它在JMeter中没有用。以下是我的测试计划。我已使用别名替换原始URL,域和凭据。

<?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>

让JMeter工作让我很沮丧。我已经尝试过HttpClient3.1&amp;的实现。 4;他们都没有工作。然后我下载了源代码,看看有什么我可以挖掘的。

这两个类处理JMeter的HTTP实现:

  • org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl(适用于HttpClient4)
  • org.apache.jmeter.protocol.http.sampler.HTTPHC3Impl(适用于HttpClient3.1)

我没有发现任何错误。

我尝试通过Java代码进行身份验证。以下是使用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);
    }
}

这段代码一次或两次返回HTTP 401,大部分时间我都收到HTTP 200。

以下是使用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());
        }
    }
}

这总是返回Http 401 Unauthorized错误。此代码取自使用JCIFS的HTTP Components site

我无法找到任何未授权的原因。 JMeter或HTTPClient是否完全支持NTLM身份验证?我在阅读上述网站上的这个说明后有些疑问:

  

NTLM是Microsoft和Microsoft开发的专有身份验证方案   针对Windows操作系统进行了优化

     

直到2008年才有   官方的,公开的,完整的协议文档。   非官方的第三方协议描述存在的结果   逆向工程的努力。真的不知道是不是   基于逆向工程的协议是完整的甚至是完整的   正确的。

     

Microsoft发布了MS-NLMP和MS-NTHT规范   2008年2月作为其互操作性原则倡议的一部分。

     

从版本4.1开始的HttpClient最初支持NTLMv1,NTLMv2和   NTLM2SessionResponse认证协议,基于相反的   工程方法。从版本4.2.3开始,HttpClient现在支持一个   更正确的实现,很大程度上基于微软自己的实现   规格。这有望纠正一些问题,   特别是自从Microsoft(Windows Server 2008 R2)开始使用以来   其协议的新实现。这个新的微软   实施导致了某些情况下的身份验证失败   NTLM的一些较旧的反向工程客户端实现。

     

已经尝试过新的HttpClient NTLM实现   成功对抗至少以下系统:

     
      
  • Windows Server 2000和Server 2003系统,配置为使用LM和NTLMv1   认证
  •   
  • Windows Server 2003系统,配置为使用NTLMv2   认证
  •   
  • 配置为使用的Windows Server 2008 R2系统   NTLM2SessionResponse身份验证
  •   
     

如果当前的HttpClient NTLM实现在您的环境中存在问题,我们肯定希望了解它。

在浏览器中,当我浏览用于身份验证的URL时,它要求提供凭据,然后导航到主页。还有另一个中间页面,它返回HTTP 302以进行重定向。

任何建议都对我有很大的帮助。

更新

在JMeter中,以下是我通过响应标题获得的结果:

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

2 个答案:

答案 0 :(得分:0)

确实在HTTP Authorization Manager

中提供了用户名,密码和域

有关详细说明和配置详细信息,请参阅Windows Authentication with Apache JMeter指南。

答案 1 :(得分:-3)

由于其专有性,没有人完全支持NTLM,除了微软。