9

I'm trying to use JDK 11 HttpClient to make requests through a corporate proxy which requires authentication by login and password. According to JDK's intro, I'm building an instance of client by means of:

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

, where authenticator is:

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

And then I execute the request itself:

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

But instead of valid response from the target server (https://httpwg.org) I receive HTTP 407 (Proxy Authentication Required), i.e. HttpClient does not use the provided authenticator.

I've tried various solutions mentioned here and here but none of them helped.

What is the correct way to make it work?

Naman
  • 27,789
  • 26
  • 218
  • 353
Toparvion
  • 799
  • 2
  • 9
  • 19
  • It seems that it works when visit a http site, but not https. Have you found any solution yet? – Keijack May 19 '20 at 08:23
  • @Keijack, for that particular application I had to use Apache HTTP client because it was the only one capable of handling the NTLM authentication. But I still wonder how to make it work with JDK HttpClient. – Toparvion May 20 '20 at 02:40
  • Have you tried with system property `-Djava.net.useSystemProxies=true`? – Marco Carlo Moriggi Apr 12 '22 at 08:32

3 Answers3

8

You have to set the "Proxy-Authorization" header on the request.

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();
hbelmiro
  • 987
  • 11
  • 31
  • 5
    after `System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");` and if the proxy use Basic authorization, this works. – Keijack May 19 '20 at 09:53
  • @hbelmiro, finally I had to use another HTTP client so I didn't check your answer. But still thank you for it! – Toparvion May 20 '20 at 02:44
  • I confirm it works with Java 11 and assuming the system property "jdk.http.auth.tunneling.disabledSchemes" is not incluging "Basic". – Krzysztof Tomaszewski Nov 05 '20 at 13:23
  • It should be noted that doing this effectively bypasses the Authenticator API provided by the JDK. Since Basic auth is dead simple this is a straight forward solution. I observe the same behaviour, and I was curious why the documented solution does not work, and traced it down to my corporate proxy server returning multiple values in the Proxy-Authenticate header, one of which was Basic but AuthenticationFilter.response() will silently fail to apply any authentication if the first header returned is not Basic. – Noa Resare Aug 08 '21 at 14:59
5

By default, basic authentication with the proxy is disabled when tunneling through an authenticating proxy since java 8u111.

You can re-enable it by specifying -Djdk.http.auth.tunneling.disabledSchemes="" on the java command line.

See the jdk 8u111 release notes

Thomas Fritsch
  • 9,639
  • 33
  • 37
  • 49
daniel
  • 2,665
  • 1
  • 8
  • 18
  • It looks like solution, but doesn't work for some reason... I've tried to use related `jdk.http.auth.proxying.disabledSchemes` flag but it also has no effect. – Toparvion Nov 19 '18 at 03:00
  • 1
    Do the proxy and the server both require authentication, and if so do they require different credentials? If that's the case then your Authenticator needs to take into account the host/address before returning the appropriate credentials. Also you may want to check that the proxy requests Basic authentication, as this is the only scheme supported by default by the stack. – daniel Nov 19 '18 at 14:44
  • daniel, yes, in this particular case both the proxy and the server require authentication. I've tried to put debugger breakpoints into Authenticator code but none of them triggered so there is no difference which host/address it was targeted against. Then I've dived into traffic dump and found out that currently this proxy doesn't request Basic authentication scheme (but NTLM and Negotiate only). To confirm it I've used Apache HTTP client and it has managed to handle NTLM authentication correctly. Will investigate it further. Thank you for your time! – Toparvion Nov 19 '18 at 14:55
1

Use

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

Construct your http client with proxy selector and authenticator

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

In your Authenticator override

@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
}
pepemsk
  • 11
  • 2