簡體   English   中英

NTLM和Java Apache httpclient 4.5.6的多線程問題

[英]multithread issue with NTLM and Java Apache httpclient 4.5.6

我有一個獨立的Java客戶端,試圖通過NTLM代理執行RMI。 它是多線程的。 我正在使用Apache httpclient 4.5.6。 我有一個5分鍾的超時周期的代理。

基本情況是可行的,只要受到代理的挑戰,每5分鍾就會重新進行身份驗證,只要兩個線程在代理超時的確切時間未同時發出請求即可。 然后失敗。 一旦失敗,所有后續嘗試都會失敗。

我附上了wireshark屏幕截圖以進行澄清(屏幕截圖來自4.5.2,但我升級到4.5.6並看到了相同的行為)。

Wireshark屏幕截圖

一個好的周期看起來像

  • 客戶端嘗試連接(無NTML標志)
  • 代理回復407(無NTML標志)
  • 客戶端再次嘗試使用ntlm消息類型NTLMSSP_NEGOTIATE進行CONNECT
  • 代理回復407 NTLMSSP_CHALLENGE
  • 客戶端確實使用NTLMSSP_AUTH和我的憑據進行連接。
  • 代理回復200,我們很高興再繼續5分鍾。

糟糕的周期看起來像

  • 客戶端嘗試連接(無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)

有什么想法可以防止這種情況發生或如何解決或從中恢復嗎? (除了同步通話,這會大大降低已經很慢的應用的運行速度)

該應用程序的一些代碼段:

// 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);

HttpContext實例是完全線程安全的。 但是,存儲在上下文中的某些屬性(如身份驗證握手狀態)顯然不是。 確保HttpContext實例不會同時更新,並且該問題應該消失。

謝謝奧列格(Oleg),這是我所做的,到目前為止,它似乎一直在起作用(對於您的答案發表評論的時間太長,但我想分享我的代碼)

// 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());

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM