Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication Proxy seems to not work with HttpURLConnection #1142

Closed
tiboun opened this issue Jan 20, 2022 · 6 comments
Closed

Authentication Proxy seems to not work with HttpURLConnection #1142

tiboun opened this issue Jan 20, 2022 · 6 comments

Comments

@tiboun
Copy link

tiboun commented Jan 20, 2022

Describe the issue
Setting up a proxy with required authentication seems to be ignored when using HttpURLConnection. I should probably missing something.

What you are trying to do
Trying to test corporate proxy with authentication requirement in order to have a good integration test.

MockServer version
5.11.1

To Reproduce

  @Test
  public void shouldNotConnectToSecurePortRequiringAuthenticationWithHttpURLConnection() throws Exception {
    int mockServerPort = new MockServer().getLocalPort();
    String existingUsername = ConfigurationProperties.proxyAuthenticationUsername();
    String existingPassword = ConfigurationProperties.proxyAuthenticationPassword();
    ClientAndServer mockServer = ClientAndServer.startClientAndServer();
    try {
      String username = UUIDService.getUUID();
      String password = UUIDService.getUUID();
      ConfigurationProperties.proxyAuthenticationUsername(username);
      ConfigurationProperties.proxyAuthenticationPassword(password);

      try (Socket socket = new Socket("127.0.0.1", mockServerPort)) {
        // given
        OutputStream output = socket.getOutputStream();

        // when
        output.write(("" +
                "CONNECT 127.0.0.1:443 HTTP/1.1\r\n" +
                "Host: 127.0.0.1:" + mockServer.getPort() + "\r\n" +
                "\r\n"
        ).getBytes(UTF_8));
        output.flush();

        // then
        System.out.println(IOStreamUtils.readInputStreamToString(socket));
        mockServer.when(
                request().withPath("/subjects")
        ).respond(
                response().withHeaders(
                        new Header(CONTENT_TYPE.toString(), MediaType.APPLICATION_JSON.toString())
                ).withBody(
                        "[\"abc\"]"
                )
        );
        System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
        System.setProperty("jdk.http.auth.proxying.disabledSchemes", "");
        Authenticator.setDefault(new Authenticator() {
          @Override
          protected PasswordAuthentication getPasswordAuthentication() {
            System.out.println(getRequestorType().toString());
            if (getRequestorType() == RequestorType.PROXY) {
              System.out.println("return password authentication");
              String randomPassword = UUIDService.getUUID();
                return new PasswordAuthentication(username, randomPassword.toCharArray());
            }
            return null;
          }
        });
        URL url = new URL("http://localhost:" + mockServer.getPort() + "/subjects");
        HttpURLConnection con = (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", mockServerPort)));
        System.out.println(con.getResponseCode());
      }
    } finally {
      ConfigurationProperties.proxyAuthenticationUsername(existingUsername);
      ConfigurationProperties.proxyAuthenticationPassword(existingPassword);
    }
  }

Expected behaviour
I expect the request to use the Authenticator defined.

@jamesdbloom
Copy link
Collaborator

The problem is that you are sending a request using HTTP and not HTTPS so proxy authentication doesn't kick in. When you send a request using HTTP the JDK client just re-writes the target socket and does nothing else. In effect the proxying is done client side and so there is no way for MockServer to authenticate that. As soon as you switch the protocol to HTTPS as shown below the authentication works as expected. Also the initial CONNECT request shows the expected response you get back from MockServer for a CONNECT request. As soon as the protocol is switched to HTTPS that the Java JDK sends the CONNECT request and gets the expected authentication error i.e. 407.

@Test
public void shouldNotConnectToSecurePortRequiringAuthenticationWithHttpURLConnection() throws Exception {
    int mockServerPort = new MockServer().getLocalPort();
    String existingUsername = ConfigurationProperties.proxyAuthenticationUsername();
    String existingPassword = ConfigurationProperties.proxyAuthenticationPassword();
    ClientAndServer mockServer = ClientAndServer.startClientAndServer();
    try {
        String username = UUIDService.getUUID();
        String password = UUIDService.getUUID();
        ConfigurationProperties.proxyAuthenticationUsername(username);
        ConfigurationProperties.proxyAuthenticationPassword(password);

        try (Socket socket = new Socket("127.0.0.1", mockServerPort)) {
            // given
            OutputStream output = socket.getOutputStream();

            // when
            output.write(("" +
                "CONNECT 127.0.0.1:443 HTTP/1.1\r\n" +
                "Host: 127.0.0.1:" + mockServer.getPort() + "\r\n" +
                "\r\n"
            ).getBytes(UTF_8));
            output.flush();

            // then
            System.out.println(IOStreamUtils.readInputStreamToString(socket));
            mockServer.when(
                request().withPath("/subjects")
            ).respond(
                response().withHeaders(
                    new Header(CONTENT_TYPE.toString(), MediaType.APPLICATION_JSON.toString())
                ).withBody(
                    "[\"abc\"]"
                )
            );
            System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
            System.setProperty("jdk.http.auth.proxying.disabledSchemes", "");
            Authenticator.setDefault(new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    System.out.println(getRequestorType().toString());
                    if (getRequestorType() == RequestorType.PROXY) {
                        System.out.println("return password authentication");
                        String randomPassword = UUIDService.getUUID();
                        return new PasswordAuthentication(username, randomPassword.toCharArray());
                    }
                    return null;
                }
            });
            URL url = new URL("https://localhost:" + mockServer.getPort() + "/subjects");
            HttpURLConnection con = (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", mockServerPort)));
            System.out.println(con.getResponseCode());
        }
    } finally {
        ConfigurationProperties.proxyAuthenticationUsername(existingUsername);
        ConfigurationProperties.proxyAuthenticationPassword(existingPassword);
    }
}

The only change I made was to update the protocol as follows, changing:

new URL("http://localhost:" + mockServer.getPort() + "/subjects");

to:

new URL("https://localhost:" + mockServer.getPort() + "/subjects");

@tiboun
Copy link
Author

tiboun commented Jan 25, 2022

Hi jamesdbloom,

Thanks for you answer. Do you mean that proxy authentication is only available for https or did I misunderstood ?

As far as I know, we can require authentication on proxy wether it's http or https.

@jamesdbloom jamesdbloom removed the next label Jan 25, 2022
@jamesdbloom
Copy link
Collaborator

Yes MockServer only supports authentication for HTTPS or SOCKS, the main issue being how would MockServer know that a request was being proxied if it is HTTP only because that request could equally be directed at MockServer as a target server not just a proxy.

I am however considering adding authentication in general to MockServer, it also might be a good idea to provide a configuration setting to make MockServer assume first it is a proxy by proxying any request that doesn't match an expectation (instead of returning a 404) in that case the proxy authentication could be enabled.

I'm going to re-open this ticket to add that configuration property which would mean when enabled it was possible to authenticate HTTP proxied requests.

@jamesdbloom
Copy link
Collaborator

I have released it is possible to detect accurately when MockServer is proxying an HTTP request so I have added support for authentication which will be shortly pushed to close this ticket.

@tiboun
Copy link
Author

tiboun commented Jan 27, 2022

Thank you very much jamesdbloom ! You're awesome !

@kavithareddyedula
Copy link

0

this code snippet worked for me .. thank you for the help

import java.net.*

System.setProperty("http.proxyUser", "userid");
System.setProperty("http.proxyPassword", "mmmmm");
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
val url = URL(uploadUrl)
val proxyInet = InetSocketAddress(proxySettings.url, Utility.getPort())
val proxy = Proxy(Proxy.Type.HTTP, proxyInet)
var uname_pwd = "userid"+":"+"mmmmm"
val authString:String = "Basic " +Base64.getEncoder().encodeToString(uname_pwd.toByteArray())
val authenticator = object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication("userid", "mmmmmmm".toCharArray())
}
}
Authenticator.setDefault(authenticator)
val connection = url.openConnection(proxy) as HttpsURLConnection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants