Skip to content

Commit f8fa536

Browse files
committed
Fix to send the proxy-authorization header when a CONNECT is being made AsyncHttpClient#1152
1 parent d01eeca commit f8fa536

7 files changed

+390
-1
lines changed

src/main/java/com/ning/http/util/AuthenticatorUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public static String perRequestProxyAuthorizationHeader(Request request,
123123

124124
String proxyAuthorization = null;
125125

126-
if (!connect && proxyServer != null && proxyServer.getPrincipal() != null
126+
if (connect && proxyServer != null && proxyServer.getPrincipal() != null
127127
&& proxyServer.getScheme() == Realm.AuthScheme.BASIC) {
128128
proxyAuthorization = computeBasicAuthentication(proxyServer);
129129

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package com.ning.http.client.async;
15+
16+
import com.ning.http.client.AsyncHttpClient;
17+
import com.ning.http.client.AsyncHttpClientConfig;
18+
import com.ning.http.client.ProxyServer;
19+
import com.ning.http.client.Realm;
20+
import com.ning.http.client.Realm.AuthScheme;
21+
import com.ning.http.client.Request;
22+
import com.ning.http.client.RequestBuilder;
23+
import com.ning.http.client.Response;
24+
25+
import java.io.IOException;
26+
import java.net.UnknownHostException;
27+
import java.util.concurrent.ExecutionException;
28+
import java.util.concurrent.Future;
29+
30+
import javax.servlet.ServletException;
31+
import javax.servlet.http.HttpServletRequest;
32+
import javax.servlet.http.HttpServletResponse;
33+
34+
import org.eclipse.jetty.server.Connector;
35+
import org.eclipse.jetty.server.Server;
36+
import org.eclipse.jetty.server.handler.AbstractHandler;
37+
import org.eclipse.jetty.server.nio.SelectChannelConnector;
38+
import org.testng.Assert;
39+
import org.testng.annotations.AfterClass;
40+
import org.testng.annotations.BeforeClass;
41+
import org.testng.annotations.Test;
42+
43+
/**
44+
* Test that validates that when having an HTTP proxy and trying to access an HTTP through the proxy the
45+
* proxy credentials should be passed after it gets a 407 response.
46+
*/
47+
public abstract class BasicHttpProxyToHttpTest extends AbstractBasicTest {
48+
49+
private Server server2;
50+
51+
public static class ProxyHTTPHandler extends AbstractHandler {
52+
53+
@Override
54+
public void handle(String pathInContext, org.eclipse.jetty.server.Request request, HttpServletRequest httpRequest,
55+
HttpServletResponse httpResponse) throws IOException, ServletException {
56+
57+
String authorization = httpRequest.getHeader("Proxy-Authorization");
58+
if (authorization == null) {
59+
httpResponse.setStatus(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
60+
httpResponse.setHeader("Proxy-Authenticate", "Basic realm=\"Fake Realm\"");
61+
} else if (authorization
62+
.equals("Basic am9obmRvZTpwYXNz")) {
63+
httpResponse.addHeader("target", request.getUri().toString());
64+
httpResponse.setStatus(HttpServletResponse.SC_OK);
65+
} else {
66+
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
67+
}
68+
httpResponse.getOutputStream().flush();
69+
httpResponse.getOutputStream().close();
70+
request.setHandled(true);
71+
}
72+
}
73+
74+
@AfterClass(alwaysRun = true)
75+
public void tearDownGlobal() throws Exception {
76+
server.stop();
77+
server2.stop();
78+
}
79+
80+
@BeforeClass(alwaysRun = true)
81+
public void setUpGlobal() throws Exception {
82+
// HTTP Server
83+
server = new Server();
84+
// HTTP Proxy Server
85+
server2 = new Server();
86+
87+
port1 = findFreePort();
88+
port2 = findFreePort();
89+
90+
// HTTP Server
91+
Connector listener = new SelectChannelConnector();
92+
93+
listener.setHost("127.0.0.1");
94+
listener.setPort(port1);
95+
server.addConnector(listener);
96+
server.setHandler(new EchoHandler());
97+
server.start();
98+
99+
listener = new SelectChannelConnector();
100+
101+
// Proxy Server configuration
102+
listener.setHost("127.0.0.1");
103+
listener.setPort(port2);
104+
server2.addConnector(listener);
105+
server2.setHandler(configureHandler());
106+
server2.start();
107+
log.info("Local HTTP Server (" + port1 + "), HTTPS Server (" + port2 + ") started successfully");
108+
}
109+
110+
111+
@Override
112+
public AbstractHandler configureHandler() throws Exception {
113+
return new ProxyHTTPHandler();
114+
}
115+
116+
@Test
117+
public void httpProxyToHttpTargetTest() throws IOException, InterruptedException, ExecutionException {
118+
try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().build())) {
119+
Request request = new RequestBuilder("GET").setProxyServer(basicProxy()).setUrl(getTargetUrl()).setRealm(new Realm.RealmBuilder().setPrincipal("user").setPassword("passwd").build()).build();
120+
Future<Response> responseFuture = client.executeRequest(request);
121+
Response response = responseFuture.get();
122+
Assert.assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK);
123+
Assert.assertEquals(getTargetUrl(), response.getHeader("target"));
124+
}
125+
}
126+
127+
private ProxyServer basicProxy() throws UnknownHostException {
128+
ProxyServer proxyServer = new ProxyServer("127.0.0.1", port2, "johndoe", "pass");
129+
proxyServer.setScheme(AuthScheme.BASIC);
130+
return proxyServer;
131+
}
132+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package com.ning.http.client.async;
15+
16+
import com.ning.http.client.AsyncHttpClient;
17+
import com.ning.http.client.AsyncHttpClientConfig;
18+
import com.ning.http.client.ProxyServer;
19+
import com.ning.http.client.Realm;
20+
import com.ning.http.client.Realm.AuthScheme;
21+
import com.ning.http.client.Request;
22+
import com.ning.http.client.RequestBuilder;
23+
import com.ning.http.client.Response;
24+
25+
import java.io.File;
26+
import java.io.IOException;
27+
import java.net.URL;
28+
import java.net.UnknownHostException;
29+
import java.security.NoSuchAlgorithmException;
30+
import java.util.concurrent.ExecutionException;
31+
import java.util.concurrent.Future;
32+
33+
import javax.servlet.ServletException;
34+
import javax.servlet.http.HttpServletRequest;
35+
import javax.servlet.http.HttpServletResponse;
36+
37+
import org.eclipse.jetty.server.Connector;
38+
import org.eclipse.jetty.server.Server;
39+
import org.eclipse.jetty.server.handler.AbstractHandler;
40+
import org.eclipse.jetty.server.handler.ConnectHandler;
41+
import org.eclipse.jetty.server.nio.SelectChannelConnector;
42+
import org.eclipse.jetty.server.ssl.SslSocketConnector;
43+
import org.testng.Assert;
44+
import org.testng.annotations.AfterClass;
45+
import org.testng.annotations.BeforeClass;
46+
import org.testng.annotations.Test;
47+
48+
/**
49+
* Test that validates that when having an HTTP proxy and trying to access an HTTPS through the proxy the
50+
* proxy credentials should be passed during the CONNECT request.
51+
*/
52+
public abstract class BasicHttpProxyToHttpsTest extends AbstractBasicTest {
53+
54+
private Server server2;
55+
56+
@AfterClass(alwaysRun = true)
57+
public void tearDownGlobal() throws Exception {
58+
server.stop();
59+
server2.stop();
60+
}
61+
62+
@BeforeClass(alwaysRun = true)
63+
public void setUpGlobal() throws Exception {
64+
// HTTP Proxy Server
65+
server = new Server();
66+
// HTTPS Server
67+
server2 = new Server();
68+
69+
port1 = findFreePort();
70+
port2 = findFreePort();
71+
72+
// Proxy Server configuration
73+
Connector listener = new SelectChannelConnector();
74+
listener.setHost("127.0.0.1");
75+
listener.setPort(port1);
76+
server.addConnector(listener);
77+
server.setHandler(configureHandler());
78+
server.start();
79+
80+
// HTTPS Server
81+
SslSocketConnector connector = new SslSocketConnector();
82+
connector.setHost("127.0.0.1");
83+
connector.setPort(port2);
84+
85+
ClassLoader cl = getClass().getClassLoader();
86+
// override system properties
87+
URL keystoreUrl = cl.getResource("ssltest-keystore.jks");
88+
String keyStoreFile = new File(keystoreUrl.toURI()).getAbsolutePath();
89+
connector.setKeystore(keyStoreFile);
90+
connector.setKeyPassword("changeit");
91+
connector.setKeystoreType("JKS");
92+
93+
log.info("SSL keystore path: {}", keyStoreFile);
94+
95+
server2.addConnector(connector);
96+
server2.setHandler(new EchoHandler());
97+
server2.start();
98+
log.info("Local Proxy Server (" + port1 + "), HTTPS Server (" + port2 + ") started successfully");
99+
}
100+
101+
@Override
102+
public AbstractHandler configureHandler() throws Exception {
103+
return new ConnectHandler(new EchoHandler()) {
104+
@Override
105+
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException {
106+
String authorization = request.getHeader("Proxy-Authorization");
107+
if (authorization == null) {
108+
response.setStatus(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
109+
response.setHeader("Proxy-Authenticate", "Basic realm=\"Fake Realm\"");
110+
return false;
111+
} else if (authorization
112+
.equals("Basic am9obmRvZTpwYXNz")) {
113+
return true;
114+
}
115+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
116+
return false;
117+
}
118+
};
119+
}
120+
121+
@Test
122+
public void httpProxyToHttpsTargetTest() throws IOException, InterruptedException, ExecutionException, NoSuchAlgorithmException {
123+
try (AsyncHttpClient client = getAsyncHttpClient(new AsyncHttpClientConfig.Builder().setAcceptAnyCertificate(true).build())) {
124+
Request request = new RequestBuilder("GET").setProxyServer(basicProxy()).setUrl(getTargetUrl2()).setRealm(new Realm.RealmBuilder().setPrincipal("user").setPassword("passwd").build()).build();
125+
Future<Response> responseFuture = client.executeRequest(request);
126+
Response response = responseFuture.get();
127+
Assert.assertEquals(response.getStatusCode(), HttpServletResponse.SC_OK);
128+
Assert.assertEquals("127.0.0.1:" + port2, response.getHeader("x-host"));
129+
}
130+
}
131+
132+
private ProxyServer basicProxy() throws UnknownHostException {
133+
ProxyServer proxyServer = new ProxyServer("127.0.0.1", port1, "johndoe", "pass");
134+
proxyServer.setScheme(AuthScheme.BASIC);
135+
return proxyServer;
136+
}
137+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package com.ning.http.client.async.grizzly;
15+
16+
import com.ning.http.client.AsyncHttpClient;
17+
import com.ning.http.client.AsyncHttpClientConfig;
18+
import com.ning.http.client.async.BasicHttpProxyToHttpTest;
19+
import com.ning.http.client.async.ProviderUtil;
20+
21+
import org.testng.annotations.Test;
22+
23+
@Test
24+
public class GrizzlyBasicHttpProxyToHttpTest extends BasicHttpProxyToHttpTest {
25+
26+
@Override
27+
public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) {
28+
return ProviderUtil.grizzlyProvider(config);
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package com.ning.http.client.async.grizzly;
15+
16+
import com.ning.http.client.AsyncHttpClient;
17+
import com.ning.http.client.AsyncHttpClientConfig;
18+
import com.ning.http.client.async.BasicHttpProxyToHttpsTest;
19+
import com.ning.http.client.async.ProviderUtil;
20+
21+
import org.testng.annotations.Test;
22+
23+
@Test
24+
public class GrizzlyBasicHttpProxyToHttpsTest extends BasicHttpProxyToHttpsTest {
25+
26+
@Override
27+
public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) {
28+
return ProviderUtil.grizzlyProvider(config);
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package com.ning.http.client.async.netty;
15+
16+
import com.ning.http.client.AsyncHttpClient;
17+
import com.ning.http.client.AsyncHttpClientConfig;
18+
import com.ning.http.client.async.BasicHttpProxyToHttpTest;
19+
import com.ning.http.client.async.ProviderUtil;
20+
21+
import org.testng.annotations.Test;
22+
23+
@Test
24+
public class NettyBasicHttpProxyToHttpTest extends BasicHttpProxyToHttpTest {
25+
26+
@Override
27+
public AsyncHttpClient getAsyncHttpClient(AsyncHttpClientConfig config) {
28+
return ProviderUtil.nettyProvider(config);
29+
}
30+
}

0 commit comments

Comments
 (0)