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:
parent
d53766f6fe
commit
a4686bc6a4
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue