Merged branch 'jetty-9.3.x' into 'master'.

This commit is contained in:
Simone Bordet 2016-03-03 08:21:26 +01:00
commit bede684cac
6 changed files with 195 additions and 24 deletions

View File

@ -31,6 +31,7 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -40,7 +41,6 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024; public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024;
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class); 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 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 HttpClient client;
private final int maxContentLength; private final int maxContentLength;
@ -64,6 +64,8 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
protected abstract URI getAuthenticationURI(Request request); protected abstract URI getAuthenticationURI(Request request);
protected abstract String getAuthenticationAttribute();
@Override @Override
public Response.Listener getResponseListener() public Response.Listener getResponseListener()
{ {
@ -92,8 +94,9 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
return; return;
} }
String authenticationAttribute = getAuthenticationAttribute();
HttpConversation conversation = request.getConversation(); 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 // We have already tried to authenticate, but we failed again
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
@ -146,18 +149,16 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
return; return;
} }
conversation.setAttribute(AUTHENTICATION_ATTRIBUTE, true); conversation.setAttribute(authenticationAttribute, true);
Request newRequest = client.copyRequest(request, request.getURI()); Request newRequest = client.copyRequest(request, request.getURI());
authnResult.apply(newRequest); authnResult.apply(newRequest);
newRequest.onResponseSuccess(new Response.SuccessListener() // Copy existing, explicitly set, authorization headers.
{ copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION);
@Override copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION);
public void onSuccess(Response response)
{ newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult))
client.getAuthenticationStore().addAuthenticationResult(authnResult); .send(null);
}
}).send(null);
} }
catch (Throwable x) catch (Throwable x)
{ {
@ -167,6 +168,13 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
} }
} }
private void copyIfAbsent(HttpRequest oldRequest, Request newRequest, HttpHeader header)
{
HttpField field = oldRequest.getHeaders().getField(header);
if (field != null && !newRequest.getHeaders().contains(header))
newRequest.getHeaders().put(field);
}
private void forwardSuccessComplete(HttpRequest request, Response response) private void forwardSuccessComplete(HttpRequest request, Response response)
{ {
HttpConversation conversation = request.getConversation(); HttpConversation conversation = request.getConversation();

View File

@ -153,14 +153,9 @@ public abstract class HttpConnection implements Connection
request.header(HttpHeader.COOKIE.asString(), cookies.toString()); request.header(HttpHeader.COOKIE.asString(), cookies.toString());
} }
// Authorization // Authentication
URI authenticationURI = proxy != null ? proxy.getURI() : request.getURI(); applyAuthentication(request, proxy != null ? proxy.getURI() : null);
if (authenticationURI != null) applyAuthentication(request, request.getURI());
{
Authentication.Result authnResult = getHttpClient().getAuthenticationStore().findAuthenticationResult(authenticationURI);
if (authnResult != null)
authnResult.apply(request);
}
} }
private StringBuilder convertCookies(List<HttpCookie> cookies, StringBuilder builder) private StringBuilder convertCookies(List<HttpCookie> cookies, StringBuilder builder)
@ -177,6 +172,16 @@ public abstract class HttpConnection implements Connection
return builder; 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) protected SendFailure send(HttpChannel channel, HttpExchange exchange)
{ {
// Forbid idle timeouts for the time window where // Forbid idle timeouts for the time window where

View File

@ -34,6 +34,7 @@ import org.eclipse.jetty.http.HttpStatus;
public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHandler public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHandler
{ {
public static final String NAME = "proxy-authenticate"; public static final String NAME = "proxy-authenticate";
private static final String ATTRIBUTE = ProxyAuthenticationProtocolHandler.class.getName() + ".attribute";
public ProxyAuthenticationProtocolHandler(HttpClient client) public ProxyAuthenticationProtocolHandler(HttpClient client)
{ {
@ -76,4 +77,10 @@ public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHa
ProxyConfiguration.Proxy proxy = destination.getProxy(); ProxyConfiguration.Proxy proxy = destination.getProxy();
return proxy != null ? proxy.getURI() : request.getURI(); return proxy != null ? proxy.getURI() : request.getURI();
} }
@Override
protected String getAuthenticationAttribute()
{
return ATTRIBUTE;
}
} }

View File

@ -34,6 +34,7 @@ import org.eclipse.jetty.http.HttpStatus;
public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHandler public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHandler
{ {
public static final String NAME = "www-authenticate"; public static final String NAME = "www-authenticate";
private static final String ATTRIBUTE = WWWAuthenticationProtocolHandler.class.getName() + ".attribute";
public WWWAuthenticationProtocolHandler(HttpClient client) public WWWAuthenticationProtocolHandler(HttpClient client)
{ {
@ -74,4 +75,10 @@ public class WWWAuthenticationProtocolHandler extends AuthenticationProtocolHand
{ {
return request.getURI(); return request.getURI();
} }
@Override
protected String getAuthenticationAttribute()
{
return ATTRIBUTE;
}
} }

View File

@ -81,7 +81,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
} }
@Test @Test
public void testAuthenticatedProxiedRequest() throws Exception public void testProxyAuthentication() throws Exception
{ {
final String user = "foo"; final String user = "foo";
final String password = "bar"; final String password = "bar";
@ -160,7 +160,7 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
} }
@Test @Test
public void testAuthenticatedProxiedRequestWithRedirect() throws Exception public void testProxyAuthenticationWithRedirect() throws Exception
{ {
String user = "foo"; String user = "foo";
String password = "bar"; String password = "bar";
@ -254,4 +254,148 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
Assert.assertEquals(status, response3.getStatus()); Assert.assertEquals(status, response3.getStatus());
Assert.assertEquals(1, requests.get()); 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());
}
@Test
public void testProxyAuthenticationWithExplicitAuthorizationHeader() 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"));
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 + 204.
ContentResponse response1 = client.newRequest(serverHost, serverPort)
.scheme(scheme)
.header(HttpHeader.AUTHORIZATION, "Basic foobar")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(status, response1.getStatus());
Assert.assertEquals(2, requests.get());
// Make again the request, authentication is cached, expect 204.
requests.set(0);
ContentResponse response2 = client.newRequest(serverHost, serverPort)
.scheme(scheme)
.header(HttpHeader.AUTHORIZATION, "Basic foobar")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(status, response2.getStatus());
Assert.assertEquals(1, requests.get());
}
} }

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.server.handler; package org.eclipse.jetty.server.handler;
import static org.hamcrest.Matchers.is;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.InputStream; import java.io.InputStream;
@ -38,7 +36,9 @@ import org.junit.BeforeClass;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@Ignore("Unfixed range bug") import static org.hamcrest.Matchers.is;
@Ignore("Unfixed range bug - Issue #107")
public class ResourceHandlerRangeTest public class ResourceHandlerRangeTest
{ {
private static Server server; private static Server server;