Issue #353 (Jetty Client doesn't forward authentication headers with redirects when using proxy)

Fixed by making sure that URI matches take into account default HTTP
ports.
This commit is contained in:
Simone Bordet 2016-03-05 13:29:31 +01:00
parent d53766f6fe
commit a4686bc6a4
7 changed files with 204 additions and 44 deletions

View File

@ -26,6 +26,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.util.AbstractAuthentication;
public class HttpAuthenticationStore implements AuthenticationStore
{
@ -85,7 +86,7 @@ public class HttpAuthenticationStore implements AuthenticationStore
// TODO: I should match the longest URI
for (Map.Entry<URI, Authentication.Result> entry : results.entrySet())
{
if (uri.toString().startsWith(entry.getKey().toString()))
if (AbstractAuthentication.matchesURI(entry.getKey(), uri))
return entry.getValue();
}
return null;

View File

@ -1000,7 +1000,7 @@ public class HttpClient extends ContainerLifeCycle
return host;
}
protected int normalizePort(String scheme, int port)
public static int normalizePort(String scheme, int port)
{
return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
}

View File

@ -88,7 +88,7 @@ public class HttpRequest implements Request
this.conversation = conversation;
scheme = uri.getScheme();
host = client.normalizeHost(uri.getHost());
port = client.normalizePort(scheme, uri.getPort());
port = HttpClient.normalizePort(scheme, uri.getPort());
path = uri.getRawPath();
query = uri.getRawQuery();
extractParams(query);

View File

@ -0,0 +1,80 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.client.util;
import java.net.URI;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Authentication;
public abstract class AbstractAuthentication implements Authentication
{
private final URI uri;
private final String realm;
public AbstractAuthentication(URI uri, String realm)
{
this.uri = uri;
this.realm = realm;
}
public abstract String getType();
public URI getURI()
{
return uri;
}
public String getRealm()
{
return realm;
}
@Override
public boolean matches(String type, URI uri, String realm)
{
if (!getType().equalsIgnoreCase(type))
return false;
if (!this.realm.equals(realm))
return false;
return matchesURI(this.uri, uri);
}
public static boolean matchesURI(URI uri1, URI uri2)
{
String scheme = uri1.getScheme();
if (uri1.getScheme().equalsIgnoreCase(scheme))
{
if (uri1.getHost().equalsIgnoreCase(uri2.getHost()))
{
// Handle default HTTP ports.
int thisPort = HttpClient.normalizePort(scheme, uri1.getPort());
int thatPort = HttpClient.normalizePort(scheme, uri2.getPort());
if (thisPort == thatPort)
{
// Use decoded URI paths.
return uri2.getPath().startsWith(uri1.getPath());
}
}
}
return false;
}
}

View File

@ -21,7 +21,8 @@ package org.eclipse.jetty.client.util;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader;
@ -35,10 +36,8 @@ import org.eclipse.jetty.util.B64Code;
* {@link AuthenticationStore} retrieved from the {@link HttpClient}
* via {@link HttpClient#getAuthenticationStore()}.
*/
public class BasicAuthentication implements Authentication
public class BasicAuthentication extends AbstractAuthentication
{
private final URI uri;
private final String realm;
private final String user;
private final String password;
@ -50,48 +49,39 @@ public class BasicAuthentication implements Authentication
*/
public BasicAuthentication(URI uri, String realm, String user, String password)
{
this.uri = uri;
this.realm = realm;
super(uri, realm);
this.user = user;
this.password = password;
}
@Override
public boolean matches(String type, URI uri, String realm)
public String getType()
{
if (!"basic".equalsIgnoreCase(type))
return false;
if (!uri.toString().startsWith(this.uri.toString()))
return false;
return this.realm.equals(realm);
return "Basic";
}
@Override
public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context)
{
String value = "Basic " + B64Code.encode(user + ":" + password, StandardCharsets.ISO_8859_1);
return new BasicResult(headerInfo.getHeader(), uri, value);
return new BasicResult(headerInfo.getHeader(), value);
}
private static class BasicResult implements Result
private class BasicResult implements Result
{
private final HttpHeader header;
private final URI uri;
private final String value;
public BasicResult(HttpHeader header, URI uri, String value)
public BasicResult(HttpHeader header, String value)
{
this.header = header;
this.uri = uri;
this.value = value;
}
@Override
public URI getURI()
{
return uri;
return BasicAuthentication.this.getURI();
}
@Override
@ -103,7 +93,7 @@ public class BasicAuthentication implements Authentication
@Override
public String toString()
{
return String.format("Basic authentication result for %s", uri);
return String.format("Basic authentication result for %s", getURI());
}
}
}

View File

@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@ -33,7 +32,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader;
@ -48,12 +48,10 @@ import org.eclipse.jetty.util.TypeUtil;
* {@link AuthenticationStore} retrieved from the {@link HttpClient}
* via {@link HttpClient#getAuthenticationStore()}.
*/
public class DigestAuthentication implements Authentication
public class DigestAuthentication extends AbstractAuthentication
{
private static final Pattern PARAM_PATTERN = Pattern.compile("([^=]+)=(.*)");
private final URI uri;
private final String realm;
private final String user;
private final String password;
@ -65,22 +63,15 @@ public class DigestAuthentication implements Authentication
*/
public DigestAuthentication(URI uri, String realm, String user, String password)
{
this.uri = uri;
this.realm = realm;
super(uri, realm);
this.user = user;
this.password = password;
}
@Override
public boolean matches(String type, URI uri, String realm)
public String getType()
{
if (!"digest".equalsIgnoreCase(type))
return false;
if (!uri.toString().startsWith(this.uri.toString()))
return false;
return this.realm.equals(realm);
return "Digest";
}
@Override
@ -108,7 +99,7 @@ public class DigestAuthentication implements Authentication
clientQOP = "auth-int";
}
return new DigestResult(headerInfo.getHeader(), uri, response.getContent(), realm, user, password, algorithm, nonce, clientQOP, opaque);
return new DigestResult(headerInfo.getHeader(), response.getContent(), getRealm(), user, password, algorithm, nonce, clientQOP, opaque);
}
private Map<String, String> parseParameters(String wwwAuthenticate)
@ -179,7 +170,6 @@ public class DigestAuthentication implements Authentication
{
private final AtomicInteger nonceCount = new AtomicInteger();
private final HttpHeader header;
private final URI uri;
private final byte[] content;
private final String realm;
private final String user;
@ -189,10 +179,9 @@ public class DigestAuthentication implements Authentication
private final String qop;
private final String opaque;
public DigestResult(HttpHeader header, URI uri, byte[] content, String realm, String user, String password, String algorithm, String nonce, String qop, String opaque)
public DigestResult(HttpHeader header, byte[] content, String realm, String user, String password, String algorithm, String nonce, String qop, String opaque)
{
this.header = header;
this.uri = uri;
this.content = content;
this.realm = realm;
this.user = user;
@ -206,7 +195,7 @@ public class DigestAuthentication implements Authentication
@Override
public URI getURI()
{
return uri;
return DigestAuthentication.this.getURI();
}
@Override

View File

@ -0,0 +1,100 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.client;
import java.net.URI;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.DigestAuthentication;
import org.junit.Assert;
import org.junit.Test;
public class HttpAuthenticationStoreTest
{
@Test
public void testFindAuthenticationWithDefaultHTTPPort() throws Exception
{
AuthenticationStore store = new HttpAuthenticationStore();
URI uri1 = URI.create("http://host:80");
URI uri2 = URI.create("http://host");
String realm = "realm";
store.addAuthentication(new BasicAuthentication(uri1, realm, "user", "password"));
Authentication result = store.findAuthentication("Basic", uri2, realm);
Assert.assertNotNull(result);
store.clearAuthentications();
// Flip the URIs.
uri1 = URI.create("https://server/");
uri2 = URI.create("https://server:443/path");
store.addAuthentication(new DigestAuthentication(uri1, realm, "user", "password"));
result = store.findAuthentication("Digest", uri2, realm);
Assert.assertNotNull(result);
}
@Test
public void testFindAuthenticationResultWithDefaultHTTPPort() throws Exception
{
AuthenticationStore store = new HttpAuthenticationStore();
store.addAuthenticationResult(new Authentication.Result()
{
@Override
public URI getURI()
{
return URI.create("http://host:80");
}
@Override
public void apply(Request request)
{
}
});
URI uri2 = URI.create("http://host");
Authentication.Result result = store.findAuthenticationResult(uri2);
Assert.assertNotNull(result);
store.clearAuthenticationResults();
// Flip the URIs.
store.addAuthenticationResult(new Authentication.Result()
{
@Override
public URI getURI()
{
return URI.create("https://server/");
}
@Override
public void apply(Request request)
{
}
});
uri2 = URI.create("https://server:443/path");
result = store.findAuthenticationResult(uri2);
Assert.assertNotNull(result);
}
}