【问题标题】:multithread issue with NTLM and Java Apache httpclient 4.5.6NTLM 和 Java Apache httpclient 4.5.6 的多线程问题
【发布时间】:2018-08-14 23:51:28
【问题描述】:

我有一个独立的 Java 客户端尝试通过 NTLM 代理执行 RMI。 它是多线程的。 我正在使用 Apache httpclient 4.5.6。 我有一个 5 分钟超时周期的代理。

基本情况有效,只要 2 个线程没有在代理超时的确切时间同时发出请求,代理就会每 5 分钟重新验证一次。然后它失败了。一旦失败,所有后续尝试都会失败。

我附上了一个 wireshark 截图来澄清(截图来自 4.5.2,但我升级到 4.5.6 并看到了相同的行为)。

一个好的循环看起来像

  • 客户端尝试 CONNECT(无 NTML 标志)
  • 代理回复 407(无 NTML 标志)
  • 客户端再次尝试使用 ntlm 消息类型 NTLMSSP_NEGOTIATE 连接
  • 代理回复 407 NTLMSSP_CHALLENGE
  • 客户端使用 NTLMSSP_AUTH 和我的凭据进行连接。
  • 代理回复 200,我们可以再等 5 分钟。

一个糟糕的循环看起来像

  • 客户端尝试 CONNECT(无 NTML 标志)
  • 代理回复 407(无 NTML 标志)
  • 客户端再次尝试使用 ntlm 消息类型 NTLMSSP_NEGOTIATE 连接
  • 客户端尝试 CONNECT(无 NTML 标志)
  • 代理回复 407(无 NTML 标志)
  • 代理回复 407 NTLMSSP_CHALLENGE
  • 几秒钟内就会出现一大堆没有 NTML 标志的 CONNECT 和 407。

对我来说,这看起来像是非线程安全代码中的多线程竞争条件。

使用 Apache httpclient 4.5.2 它只是传播了 407,我在 CloseableHttpResponse.getStatusLine().getStatusCode() 中检测到它。 使用 Apache httpclient 4.5.6 我看到了这个

java.lang.IllegalStateException: Auth scheme is null
    at org.apache.http.util.Asserts.notNull(Asserts.java:52)
    at org.apache.http.impl.auth.HttpAuthenticator.ensureAuthScheme(HttpAuthenticator.java:229)
    at org.apache.http.impl.auth.HttpAuthenticator.generateAuthResponse(HttpAuthenticator.java:184)
    at org.apache.http.impl.execchain.MainClientExec.createTunnelToTarget(MainClientExec.java:484)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:411)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)

任何想法如何防止这种情况或解决它或从中恢复? (除了通话同步,这会大大降低已经很慢的应用程序)

应用中的一些代码 sn-ps:

// this is done only once
HttpClientBuilder builder = HttpClients.custom();
SocketConfig.Builder socketConfig = SocketConfig.custom();
RequestConfig.Builder requestConfig = RequestConfig.custom();
HttpHost proxy = new HttpHost(proxyHost, proxyPort);
builder.setProxy(proxy);
requestConfig.setProxy(proxy);
builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
String localHost = getLocalHostname();
credentialsProvider.setCredentials(
    new AuthScope(proxyHost, proxyPort, AuthScope.ANY_REALM, "ntlm"),
    new NTCredentials(user, password, localHost, domain));
builder.setDefaultCredentialsProvider(credentialsProvider);
builder.setDefaultSocketConfig(socketConfig.build());
builder.setDefaultRequestConfig(requestConfig.build());
CloseableHttpClient client = builder.build();

...

// cached, we use the same one every time in accordance with section 4.7 of
// https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/authentication.html
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credentialsProvider);

...

// new HttpPost every time
HttpPost postMethod = new HttpPost(uri);
postMethod.setEntity(new ByteArrayEntity(bytesOut.toByteArray()));
response = client.execute(postMethod, context);

【问题讨论】:

标签: java multithreading apache-httpclient-4.x ntlm


【解决方案1】:

HttpContext 实例是完全线程安全的。然而,某些存储在上下文中的属性,例如身份验证握手状态,显然不是。确保HttpContext 实例不会同时更新,问题应该会消失。

【讨论】:

  • 我如何确保这一点?我的代码没有更新HttpClientContext,这是发生在 apache httpclient 代码深处的某个地方。我对上下文所做的唯一事情就是将其传递给CloseableHttpClient.execute(HttpUriRequest, HttpContext)
  • 谢谢,这就是我所做的,到目前为止它似乎正在工作。
【解决方案2】:

谢谢奥列格,这就是我所做的,到目前为止它似乎正在工作(太长了,无法对你的答案发表评论,但我想分享我的代码)

// I use the base version when not going through a proxy
public class HttpClientContextFactory {
    public HttpClientContext create() {
        return HttpClientContext.create();
    }
}

// I use this when I go through a NTLM proxy
private HttpClientContextFactory getNtlmContextFactory(
        final CredentialsProvider credentialsProvider) {
    return new HttpClientContextFactory() {
        ThreadLocal<HttpClientContext> tlContext = ThreadLocal
                .<HttpClientContext> withInitial(() -> {
                    HttpClientContext context = HttpClientContext.create();
                    context.setCredentialsProvider(credentialsProvider);
                    return context;
                });

        @Override
        public HttpClientContext create() {
            return tlContext.get();
        }
    };
}

// then do this when I connect to the server
response = client.execute(postMethod, contextFactory.create());

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-16
    • 2016-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多