【问题标题】:HttpClient 4.2.2 and proxy with username/passwordHttpClient 4.2.2 和带有用户名/密码的代理
【发布时间】:2012-11-08 11:18:55
【问题描述】:

我对 HttpClient 4.2.2 和使用用户名/密码的代理有疑问。 HttpClient 是这样工作的:

  1. 列表项
  2. 发送不带代理的请求(不过我为每个请求设置了代理参数)
  3. 从 Squid 获得 407 错误的响应
  4. 使用代理内容发送请求

这是非常奇怪的行为,是否可以在每个请求中添加有关代理的信息?我尝试在每个请求中添加硬编码的“Proxy-Authorization”标头,并且效果很好,为什么 HttpClient 不能这样做?

Java 代码

DefaultHttpClient httpClient = new DefaultHttpClient();
httpClient.getParams().setParameter(PROTOCOL_VERSION, HTTP_1_1);
...
String proxyServer = getProxyServer();
int proxyPort = getProxyPort();
List<String> authpref = new ArrayList<String>();
authpref.add(AuthPolicy.BASIC);
httpClient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
String proxyUser = getProxyUser();
String proxyPassword = getProxyPassword();
CredentialsProvider credsProvider = httpClient.getCredentialsProvider();
credsProvider.setCredentials(new AuthScope(proxyServer, proxyPort), new UsernamePasswordCredentials(proxyUser, proxyPassword));
httpClient.setCredentialsProvider(credsProvider);
HttpHost proxy = new HttpHost(proxyServer, proxyPort, (proxyServer.indexOf("https") != 0) ? "http" : "https");
httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
...
HttpPut put = new HttpPut(url);

/*** hardcoded header ***/
//put.addHeader("Proxy-Authorization", "Basic eHRlbmR4LmRuZXByOnF3ZXJ0eQ==");
/*** hardcoded header ***/

put.setEntity(entity);
httpClient.execute(put);
httpClient.getConnectionManager().shutdown();


[DefaultClientConnection] Sending request: PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image1.jpg HTTP/1.1
[headers] >> PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image1.jpg HTTP/1.1
[headers] >> Authorization: Basic eGRldjo0YTFmNmMwOTgyYWRkMWQ0NDg1YjRhMGE4YWMxY2JjMWNiMTA0ODc1
[headers] >> Content-Length: 2
[headers] >> Host: 172.26.27.22:8080
[headers] >> Proxy-Connection: Keep-Alive
[headers] >> User-Agent: Apache-HttpClient/4.2.2 (java 1.5)
[DefaultClientConnection] Receiving response: HTTP/1.0 407 Proxy Authentication Required
[headers] << HTTP/1.0 407 Proxy Authentication Required
[headers] << Server: squid/2.7.STABLE8
[headers] << Date: Thu, 08 Nov 2012 10:09:49 GMT
[headers] << Content-Type: text/html
[headers] << Content-Length: 1431
[headers] << X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0
[headers] << Proxy-Authenticate: Basic realm="Please, enter username and password"
[headers] << X-Cache: MISS from 172.26.27.94
[headers] << X-Cache-Lookup: NONE from 172.26.27.94:3128
[headers] << Via: 1.0 172.26.27.94:3128 (squid/2.7.STABLE8)
[headers] << Connection: close
[DefaultHttpClient] Authentication required
[DefaultHttpClient] 172.26.27.94:3128 requested authentication
[ProxyAuthenticationStrategy] Authentication schemes in the order of preference: [Basic]
[DefaultHttpClient] Selected authentication options: [BASIC]
[DefaultClientConnection] Connection 0.0.0.0:63344<->172.26.27.94:3128 closed
[DefaultClientConnectionOperator] Connecting to 172.26.27.94:3128
[RequestAddCookies] CookieSpec selected: best-match
[RequestAuthCache] Re-using cached 'basic' auth scheme for http://172.26.27.22:8080
[RequestAuthCache] No credentials for preemptive authentication
[RequestProxyAuthentication] Proxy auth state: CHALLENGED
[RequestProxyAuthentication] Generating response to an authentication challenge using basic scheme
[DefaultHttpClient] Attempt 2 to execute request
[DefaultClientConnection] Sending request: PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image1.jpg HTTP/1.1
[headers] >> PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image1.jpg HTTP/1.1
[headers] >> Authorization: Basic eGRldjo0YTFmNmMwOTgyYWRkMWQ0NDg1YjRhMGE4YWMxY2JjMWNiMTA0ODc1
[headers] >> Content-Length: 2
[headers] >> Host: 172.26.27.22:8080
[headers] >> Proxy-Connection: Keep-Alive
[headers] >> User-Agent: Apache-HttpClient/4.2.2 (java 1.5)
[headers] >> Proxy-Authorization: Basic eHRlbmR4LmRuZXByOnF3ZXJ0eQ==
[DefaultClientConnection] Receiving response: HTTP/1.0 201 Created
[headers] << HTTP/1.0 201 Created
[headers] << Content-Length: 0
[headers] << Date: Thu, 08 Nov 2012 10:09:49 GMT
[headers] << X-Cache: MISS from 172.26.27.94
[headers] << X-Cache-Lookup: MISS from 172.26.27.94:3128
[headers] << Via: 1.1 172.26.27.94:3128 (squid/2.7.STABLE8)
[headers] << Connection: keep-alive
[headers] << Proxy-Connection: keep-alive

鱿鱼日志

1352370666.778      0 172.26.27.94 TCP_DENIED/407 1870 PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image1.jpg - NONE/- text/html
1352370671.429      8 172.26.27.94 TCP_MISS/201 282 PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image1.jpg proxyuser DIRECT/172.26.27.22 -
1352370671.474      0 172.26.27.94 TCP_DENIED/407 1882 PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image2.jpg - NONE/- text/html
1352370671.486      7 172.26.27.94 TCP_MISS/201 282 PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image2.jpg proxyuser DIRECT/172.26.27.22 -

带有硬编码的标题

java日志中没有407错误,还有squid日志

鱿鱼日志

1352370542.016      8 172.26.27.94 TCP_MISS/201 282 PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image1.jpg proxyuser DIRECT/172.26.27.22 -
1352370542.033      7 172.26.27.94 TCP_MISS/201 282 PUT http://172.26.27.22:8080/myapp/rest/2/3/29/image2.jpg proxyuser DIRECT/172.26.27.22 -

【问题讨论】:

    标签: java proxy httpclient squid http-status-code-407


    【解决方案1】:

    使用 HTTP 代理的身份验证过程在 RFC2616 §14.33 和 §14.34 中进行了描述,并且与您在 HttpClient 中看到的完全相同。它包括:

    • 代理发送 407(需要代理身份验证) 响应,其中包含适用于所请求资源的质询的 Proxy-Authenticate 标头,
    • 客户端发出带有 Proxy-Authorization 标头的新请求,该标头由包含身份验证信息的凭据组成。

    this tutorial(第 4.8 节)中描述了使用 HttpClient 实现抢占式身份验证,并且需要预先填充客户端对象的身份验证缓存。 不幸的是,他们的代码在对代理服务器进行预身份验证时不起作用。理解如何正确设置它可能有点棘手,但实际上它就像将参数传递给 BasicScheme 构造函数一样简单:

    AuthCache authCache = new BasicAuthCache();
    
    AuthScheme basicAuthScheme = null;
    if (isProxy) {
       basicAuthScheme = new BasicScheme(ChallengeState.PROXY);
    } else {
       basicAuthScheme = new BasicScheme(ChallengeState.TARGET);
    }
    
    authCache.put(host, basicAuthScheme);
    httpContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
    

    【讨论】:

      【解决方案2】:

      这是你的 AuthScope:

      // You set proxyServer here vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
      credsProvider.setCredentials(new AuthScope(proxyServer, proxyPort), new UsernamePasswordCredentials(proxyUser, proxyPassword));
      httpClient.setCredentialsProvider(credsProvider);
      // But here you are looking for the indexof https to determine if it is an SSL proxy
      // is the String returned from getProxyServer() above a URL or a host name?
      HttpHost proxy = new HttpHost(proxyServer, proxyPort, (proxyServer.indexOf("https") != 0) ? "http" : "https");
      httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
      

      埋在我上面的评论中 - getProxyServer() 返回的字符串是 URL 还是主机名?

      编辑

      我想我已经弄清楚发生了什么。必须在每个请求上发送基本身份验证才能使其正常工作。如果您要进行多次执行,您将需要一些机制来缓存身份验证数据。通过以您现在的方式执行客户端,没有“上下文”可以存储它,因为每个请求都会创建一个。

      要查看的另一项是client tutorial 第 4.7 和 4.8 节。如果您希望完全消除 407 错误,然后是使用 BASIC 身份验证的请求,请务必阅读第 4.8 节。

      密切注意他们定义的“localcontext”变量,因为它充当您客户端的状态容器。

      【讨论】:

      • getProxyServer() 返回 IP 地址
      • 有趣...你试过没有 http/https 检查的 HttpHost 吗?我想知道这是否会产生任何影响 - 除此之外,我会尝试使用调试器进行追踪,看看它是否甚至尝试使用已配置的代理。
      • 我最近对这个客户有很多经验......我会在早上尝试设置一个环境来进一步检查 - 请继续关注,我会保持联系。
      • 刚刚再看一遍 - 我想我知道发生了什么
      • 我更新了我的答案 - 除了表明已经附加了一个方案之外,您似乎无法真正预填充缓存条目。您将在启动时始终看到 407 响应,因为它向客户端指示它未经过身份验证并且必须进行身份验证。缓存它并在第一个请求时发送它可能会引入应用程序漏洞,应该不鼓励。如果您可以在第一个请求中使用 407,但在请求之间保持本地上下文,那么您不应该看到它们过去。
      最近更新 更多