首页 文章

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

提问于
浏览
1

我正在努力让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和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错误 . 此代码取自HTTP Components site,它正在使用JCIFS .

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

NTLM是Microsoft开发的专有身份验证方案,针对Windows操作系统进行了优化 . 直到2008年,该协议还没有官方的,公开的,完整的文件 . 由于逆向工程的努力,存在非官方的第三方协议描述 . 目前尚不清楚基于逆向工程的协议是完整的还是正确的 . Microsoft于2008年2月发布了MS-NLMP和MS-NTHT规范,作为其互操作性原则计划的一部分 . 从版本4.1开始,HttpClient最初支持基于逆向工程方法的NTLMv1,NTLMv2和NTLM2SessionResponse身份验证协议 . 从版本4.2.3开始,HttpClient现在支持更正确的实现,主要基于Microsoft自己的规范 . 预计这将解决许多问题,特别是因为Microsoft(从Windows Server 2008 R2开始)开始使用其协议的新实现 . 这种新的Microsoft实现在某些情况下导致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 回答

相关问题