From 6d9b36c8a4e3000d7241348fc32aaca9a7d4ebac Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 2 Mar 2016 18:01:32 +0100 Subject: [PATCH] Issue #381 (HttpClient does not send the Authorization header with authenticating proxy) Fixed by tracking correctly the conversation attributes for authentication, and by applying both proxy authentication results and server authentication results. --- .../client/AuthenticationProtocolHandler.java | 18 ++--- .../eclipse/jetty/client/HttpConnection.java | 21 +++-- .../ProxyAuthenticationProtocolHandler.java | 7 ++ .../WWWAuthenticationProtocolHandler.java | 7 ++ .../jetty/client/HttpClientProxyTest.java | 76 ++++++++++++++++++- 5 files changed, 108 insertions(+), 21 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java index 9d7c297774e..860cdd9e26f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java @@ -40,7 +40,6 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024; public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class); private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(.*)", Pattern.CASE_INSENSITIVE); - private static final String AUTHENTICATION_ATTRIBUTE = AuthenticationProtocolHandler.class.getName() + ".authentication"; private final HttpClient client; private final int maxContentLength; @@ -64,6 +63,8 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler protected abstract URI getAuthenticationURI(Request request); + protected abstract String getAuthenticationAttribute(); + @Override public Response.Listener getResponseListener() { @@ -92,8 +93,9 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler return; } + String authenticationAttribute = getAuthenticationAttribute(); HttpConversation conversation = request.getConversation(); - if (conversation.getAttribute(AUTHENTICATION_ATTRIBUTE) != null) + if (conversation.getAttribute(authenticationAttribute) != null) { // We have already tried to authenticate, but we failed again if (LOG.isDebugEnabled()) @@ -146,18 +148,12 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler return; } - conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true); + conversation.setAttribute(authenticationAttribute, true); Request newRequest = client.copyRequest(request, request.getURI()); authnResult.apply(newRequest); - newRequest.onResponseSuccess(new Response.SuccessListener() - { - @Override - public void onSuccess(Response response) - { - client.getAuthenticationStore().addAuthenticationResult(authnResult); - } - }).send(null); + newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult)) + .send(null); } catch (Throwable x) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java index aa50e988138..4c9d4ed3590 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java @@ -153,14 +153,9 @@ public abstract class HttpConnection implements Connection request.header(HttpHeader.COOKIE.asString(), cookies.toString()); } - // Authorization - URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI(); - if (authenticationURI != null) - { - Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI); - if (authnResult != null) - authnResult.apply(request); - } + // Authentication + applyAuthentication(request, proxy != null ? proxy.getURI() : null); + applyAuthentication(request, request.getURI()); } private StringBuilder convertCookies(List cookies, StringBuilder builder) @@ -177,6 +172,16 @@ public abstract class HttpConnection implements Connection return builder; } + private void applyAuthentication(Request request, URI uri) + { + if (uri != null) + { + Authentication.Result result = getHttpClient().getAuthenticationStore().findAuthenticationResult(uri); + if (result != null) + result.apply(request); + } + } + protected SendFailure send(HttpChannel channel, HttpExchange exchange) { // Forbid idle timeouts for the time window where diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java index 4099974417c..85f2c30919d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ProxyAuthenticationProtocolHandler.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.http.HttpStatus; public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHandler { public static final String NAME = "proxy-authenticate"; + private static final String ATTRIBUTE = ProxyAuthenticationProtocolHandler.class.getName() + ".attribute"; public ProxyAuthenticationProtocolHandler(HttpClient client) { @@ -76,4 +77,10 @@ public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHa ProxyConfiguration.Proxy proxy = destination.getProxy(); return proxy != null ? proxy.getURI() : request.getURI(); } + + @Override + protected String getAuthenticationAttribute() + { + return ATTRIBUTE; + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java index 5f023a95d07..3d0ab9d849d 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/WWWAuthenticationProtocolHandler.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.http.HttpStatus; public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHandler { public static final String NAME = "www-authenticate"; + private static final String ATTRIBUTE = WWWAuthenticationProtocolHandler.class.getName() + ".attribute"; public WWWAuthenticationProtocolHandler(HttpClient client) { @@ -74,4 +75,10 @@ public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHand { return request.getURI(); } + + @Override + protected String getAuthenticationAttribute() + { + return ATTRIBUTE; + } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java index 8a8866c2d1e..2efc6c6bf25 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientProxyTest.java @@ -81,7 +81,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest } @Test - public void testAuthenticatedProxiedRequest() throws Exception + public void testProxyAuthentication() throws Exception { final String user = "foo"; final String password = "bar"; @@ -160,7 +160,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest } @Test - public void testAuthenticatedProxiedRequestWithRedirect() throws Exception + public void testProxyAuthenticationWithRedirect() throws Exception { String user = "foo"; String password = "bar"; @@ -254,4 +254,76 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest Assert.assertEquals(status, response3.getStatus()); Assert.assertEquals(1, requests.get()); } + + @Test + public void testProxyAuthenticationWithServerAuthentication() throws Exception + { + String proxyRealm = "proxyRealm"; + String serverRealm = "serverRealm"; + int status = HttpStatus.NO_CONTENT_204; + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + String authorization = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString()); + if (authorization == null) + { + response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407); + response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + proxyRealm + "\""); + } + else + { + authorization = request.getHeader(HttpHeader.AUTHORIZATION.asString()); + if (authorization == null) + { + response.setStatus(HttpStatus.UNAUTHORIZED_401); + response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Basic realm=\"" + serverRealm + "\""); + } + else + { + response.setStatus(status); + } + } + } + }); + + String proxyHost = "localhost"; + int proxyPort = connector.getLocalPort(); + String serverHost = "server"; + int serverPort = proxyPort + 1; + URI proxyURI = URI.create(scheme + "://" + proxyHost + ":" + proxyPort); + client.getAuthenticationStore().addAuthentication(new BasicAuthentication(proxyURI, proxyRealm, "proxyUser", "proxyPassword")); + URI serverURI = URI.create(scheme + "://" + serverHost + ":" + serverPort); + client.getAuthenticationStore().addAuthentication(new BasicAuthentication(serverURI, serverRealm, "serverUser", "serverPassword")); + client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort)); + final AtomicInteger requests = new AtomicInteger(); + client.getRequestListeners().add(new Request.Listener.Adapter() + { + @Override + public void onSuccess(Request request) + { + requests.incrementAndGet(); + } + }); + // Make a request, expect 407 + 401 + 204. + ContentResponse response1 = client.newRequest(serverHost, serverPort) + .scheme(scheme) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(status, response1.getStatus()); + Assert.assertEquals(3, requests.get()); + + // Make again the request, authentication is cached, expect 204. + requests.set(0); + ContentResponse response2 = client.newRequest(serverHost, serverPort) + .scheme(scheme) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(status, response2.getStatus()); + Assert.assertEquals(1, requests.get()); + } }