【发布时间】:2015-07-04 02:55:04
【问题描述】:
我正在努力让 JMeter 与 NTLM 身份验证一起使用。一开始,我得到了一个 URL 和凭据。当我在 Firefox 和 Chrome 中测试凭据时,我收到了身份验证弹出窗口,并在提供凭据后通过了身份验证。所以我创建了一个具有以下配置的测试计划:
- HTTP 授权管理器
- HTTP 请求默认值
- HTTP 请求
我不知道 NTLM 身份验证架构的域要求。所以最终 JMeter 未能通过身份验证并返回 HTTP 401 错误。
然后我尝试了 Bad boy 来录制测试脚本。当我在 Bad boy 中输入 URL 时,我收到了 Windows Authentication Popup,并且给定的凭据在 Bad boy 中不起作用。
所以我尝试了 IE 并收到了相同的 Windows 身份验证弹出窗口,但凭据不起作用。我询问了域,并在 IE 中将该域作为 Domain\Username 提供后,我成功验证了该用户。
我对 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 年,没有 协议的正式、公开、完整的文档。 由于存在非官方的第 3 方协议描述 逆向工程的努力。是否真的不知道 基于逆向工程的协议是完整的甚至是 正确。
Microsoft 发布了 MS-NLMP 和 MS-NTHT 规范 2008 年 2 月,作为其互操作性原则计划的一部分。
从 4.1 版开始,HttpClient 最初支持 NTLMv1、NTLMv2 和 NTLM2SessionResponse 身份验证协议,基于反向 工程方法。从 4.2.3 版开始,HttpClient 现在支持 更正确的实现,很大程度上基于微软自己的 规格。这有望纠正一些问题, 特别是自从微软(从 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
【问题讨论】:
-
只是阅读您在 HttpClient 网站上引用的内容,听起来确实像使用 HttpClient 4 或 3.1 的 JMeter,仍然使用较旧的(逆向工程)NTLM 实现 - 这几乎肯定会在 Windows 上引起问题服务器 2008 R2 及更高版本。您的服务器正在运行什么操作系统/IIS?
-
@RaGe 我不知道什么是OS/IIS版本,不过我会在收集信息后回复你。
标签: java jmeter apache-httpclient-4.x ntlm apache-commons-httpclient