使用JDK 11 HttpClient进行代理身份验证

9

我正在尝试使用JDK 11的HttpClient通过需要登录和密码身份验证的公司代理进行请求。根据JDK的intro,我正在通过以下方式构建客户端实例:

HttpClient httpClient = HttpClient.newBuilder()
        .version(HTTP_1_1)
        .proxy(ProxySelector.of(new InetSocketAddress("proxy.mycompany.com", 3128)))
        .authenticator(authenticator)
        .build();

其中authenticator是:

Authenticator authenticator = new Authenticator() {
  @Override
  protected PasswordAuthentication getPasswordAuthentication() {
    return new PasswordAuthentication("login", "password".toCharArray());
  }
};

然后我执行请求本身:

HttpRequest outRequest = HttpRequest.newBuilder()
        .version(HTTP_1_1)
        .GET()
        .uri(URI.create("https://httpwg.org/asset/http.svg")) // no matter which URI to request
        .build();
HttpResponse<String> inResponse = httpClient.send(outRequest, BodyHandlers.ofString());

但是,与目标服务器 (https://httpwg.org) 交互时,我收到了HTTP 407 (代理身份验证失败)的无效响应,也就是说,HttpClient没有使用提供的authenticator

我已经尝试了这里这里提到的各种解决方案,但都没有帮助。

正确的方法是什么?


似乎在访问HTTP网站时它可以工作,但是在HTTPS网站上却不行。你找到任何解决方案了吗? - Keijack
@Keijack,对于那个特定的应用程序,我不得不使用Apache HTTP客户端,因为它是唯一能够处理NTLM身份验证的客户端。但我仍然想知道如何让它与JDK HttpClient一起工作。 - Toparvion
你尝试过使用系统属性-Djava.net.useSystemProxies=true吗? - Marco Carlo Moriggi
3个回答

8

您需要在请求中设置“Proxy-Authorization”标头。

HttpClient httpClient = HttpClient.newBuilder()
        .version(HTTP_1_1)
        .proxy(ProxySelector.of(new InetSocketAddress("proxy.mycompany.com", 3128)))
        .build();

String encoded = new String(Base64.getEncoder().encode("login:password".getBytes()));

HttpRequest outRequest = HttpRequest.newBuilder()
                                    .version(HTTP_1_1)
                                    .GET()
                                    .uri(URI.create("https://httpwg.org/asset/http.svg")) // no matter which URI to request
                                    .setHeader("Proxy-Authorization", "Basic " + encoded)
                                    .build();

5
如果代理使用基本身份验证,并且在执行System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");之后,这将起作用。 - Keijack
@hbelmiro,最终我不得不使用另一个HTTP客户端,所以我没有检查你的答案。但还是谢谢你! - Toparvion
我确认它适用于Java 11,并假设系统属性“jdk.http.auth.tunneling.disabledSchemes”不包括“Basic”。 - Krzysztof Tomaszewski
需要注意的是,这样做有效地绕过了JDK提供的Authenticator API。由于基本身份验证非常简单,因此这是一个直接的解决方案。我观察到相同的行为,并且我很好奇为什么文档中提供的解决方案不起作用,并将其追溯到我的公司代理服务器在Proxy-Authenticate头中返回多个值之一是Basic,但如果第一个返回的标头不是Basic,则AuthenticationFilter.response()将默默地无法应用任何身份验证。 - Noa Resare

5

从Java 8u111开始,默认情况下,通过认证代理隧道传输时,基本身份验证与代理的连接被禁用。

您可以在Java命令行中指定-Djdk.http.auth.tunneling.disabledSchemes=""以重新启用它。

请参阅jdk 8u111发行说明


看起来是解决方案,但由于某些原因无法工作...我尝试使用相关的jdk.http.auth.proxying.disabledSchemes标志,但它也没有效果。 - Toparvion
1
代理和服务器是否都需要身份验证,如果是的话,它们需要不同的凭据吗?如果是这样,那么您的 Authenticator 需要考虑主机/地址,在返回适当的凭据之前。此外,您可能需要检查代理请求基本身份验证,因为这是堆栈默认支持的唯一方案。 - daniel
丹尼尔,是的,在这种情况下,代理和服务器都需要身份验证。我已经尝试在Authenticator代码中设置调试器断点,但没有一个触发,所以它被针对哪个主机/地址没有区别。 然后我深入了流量转储,并发现当前这个代理不请求基本身份验证方案(只有NTLM和Negotiate)。为了确认这一点,我使用了Apache HTTP客户端,它成功地处理了NTLM身份验证。将进一步调查。谢谢你的时间! - Toparvion

1

使用

System.setProperty("jdk.http.auth.proxying.disabledSchemes", "");
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");

使用代理选择器和认证器构建您的http客户端

HttpClient client = HttpClient.newBuilder()
    .authenticator(yourCustomAuthenticator)
    .proxy(yourCustomProxySelector)
    .build();

在您的身份验证器覆盖中
@Override
protected PasswordAuthentication getPasswordAuthentication() {
    if (getRequestorType().equals(RequestorType.PROXY)) {
        return getPasswordAuthentication(getRequestingHost());
    } else {
        return null;
    }
}
protected PasswordAuthentication getPasswordAuthentication(String proxyHost) {
    // your logic to find username and password for proxyHost
    return new PasswordAuthentication(proxyUsername, proxyPassword.toCharArray());
    // or return null
}

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