HTTPClient使用基本身份验证时会发送两个请求?

14

我一直在使用HTTPClient 4.1.2版本尝试访问需要基本身份验证的 REST over HTTP API。以下是客户端代码:

DefaultHttpClient httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager());
// Enable HTTP Basic Auth
httpClient.getCredentialsProvider().setCredentials(
    new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), 
    new UsernamePasswordCredentials(this.username, this.password));

HttpHost proxy = new HttpHost(this.proxyURI.getHost(), this.proxyURI.getPort());

httpClient.getParams().setParameter(ConnRouteParams.DEFAULT_PROXY, proxy);

当我构建一个像这样的POST请求:

HttpPost request = new HttpPost("http://my/url");
request.addHeader(new BasicHeader("Content-type", "application/atom+xml; type=entry")); // required by vendor
request.setEntity(new StringEntity("My content"));

HttpResponse response = client.execute(request);

我在 Charles Proxy 中看到有两个请求被发送。一个请求没有Authorization: Basic ... 头,另一个这个头。第一个请求因为缺少授权信息而失败(状态码为 401),但第二个请求则顺利通过并返回状态码 201。

有人知道这是为什么吗?谢谢!

编辑:

我应该澄清一下,我已经看过这个问题,但如您所见,我已经按照相同的方式设置了AuthScope,但没有解决我的问题。此外,我每次发送请求都会创建一个新的HttpClient(尽管我使用相同的ConnectionManager),但即使我为多个请求使用相同的HttpClient,问题仍然存在。

编辑2:

所以像 @LastCoder 建议的那样做似乎是正确的。请参见这个回答以获取更多信息。问题源于我对 HTTP 规范的认识不足。我想要实现的是“预先认证”,而HttpClient文档在这里提到了它。幸运的是,上面链接的答案是一种更简洁和清晰的方式来实现它。


1
我注意到即使指定了凭据,在使用SoapUI时也会发生相同的情况。 - Harindaka
我在想这是否真的是正常行为。客户端发出一个不带任何身份验证信息的HTTP请求,然后通过401状态码被告知需要基本身份验证。理论上,基本身份验证可以预先完成,但其他身份验证方案(例如摘要)需要额外的协商。 - seand
@seand 你知道吗,我其实考虑过这个问题,但我不确定它是否内置于HTTP协议或类似的东西中。 - daveslab
@devslab HTTP客户端有可能提前发送基本身份验证头(@LastCoder提到了如何做到这一点)。我认为HttClient不知道服务器想要基本的身份验证。你提前告诉它了凭据,但没有告诉它协议。如果有一种方法告诉它“这是基本”身份验证,那么它可能会省去额外的步骤。 - seand
原来@LastCoder发现这是HTTP规范的一部分。 - daveslab
2个回答

11

为什么不直接编码USERNAME:PASSWORD并使用 .addHeader() 添加认证标头,而不是使用 .setCredentials()?


我当然可以这样做,并且我会测试一下是否有所不同,但是根据HttpClient存储库中的示例代码(http://bit.ly/wEsEhY),这就是我应该这样做的方式。 - daveslab
7
这句话的意思是:根据HTTP客户端规范,在匿名方式下首先请求资源,并在收到401响应时使用Authorization头信息进行身份验证。如果不这样做,客户端将向Web服务器发送它不需要的Authorization头信息,从而产生垃圾信息。这是基本的安全最佳实践。 - Louis Ricci
这是一种蛮力解决方案,但如果需要切换协议(例如,如果服务器更改为使用摘要认证),则会使其更难。 - seand
1
@LastCoder的评论就是这个问题的答案。 - Juan Campa

2
这意味着您的服务器/目标终端为每个客户端请求创建一个新会话。这迫使您的每个请求都经过握手,这意味着客户端首先发出调用并意识到它需要授权,然后跟随授权。您需要做的是按以下方式预先发送授权:
httpClient.getParams().setAuthenticationPreemptive(true);
仅为了理解该过程,您可以记录客户端请求标头,以便了解客户端正在发送和接收的内容: 看看这是否有效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接