mirror of
https://github.com/apache/httpcomponents-client.git
synced 2025-02-27 21:29:12 +00:00
AuthCache conformance to RFC 7617
This commit is contained in:
parent
5390aef223
commit
30c253b37b
@ -26,14 +26,19 @@
|
||||
*/
|
||||
package org.apache.hc.client5.testing.async;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.hc.client5.http.AuthenticationStrategy;
|
||||
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
|
||||
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
|
||||
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
|
||||
import org.apache.hc.client5.http.auth.AuthCache;
|
||||
import org.apache.hc.client5.http.auth.AuthSchemeFactory;
|
||||
import org.apache.hc.client5.http.auth.AuthScope;
|
||||
import org.apache.hc.client5.http.auth.CredentialsProvider;
|
||||
@ -42,6 +47,7 @@
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
|
||||
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
|
||||
import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
|
||||
import org.apache.hc.client5.http.impl.auth.BasicScheme;
|
||||
import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
@ -52,7 +58,9 @@
|
||||
import org.apache.hc.core5.http.HttpException;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequestInterceptor;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpResponseInterceptor;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.HttpVersion;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
@ -64,9 +72,12 @@
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
|
||||
import org.apache.hc.core5.http.protocol.HttpCoreContext;
|
||||
import org.apache.hc.core5.http.support.BasicResponseBuilder;
|
||||
import org.apache.hc.core5.http2.config.H2Config;
|
||||
import org.apache.hc.core5.http2.impl.H2Processors;
|
||||
import org.apache.hc.core5.net.URIAuthority;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
@ -104,6 +115,10 @@ public final HttpHost start(
|
||||
|
||||
abstract void setTargetAuthenticationStrategy(AuthenticationStrategy targetAuthStrategy);
|
||||
|
||||
abstract void addResponseInterceptor(HttpResponseInterceptor responseInterceptor);
|
||||
|
||||
abstract void addRequestInterceptor(final HttpRequestInterceptor requestInterceptor);
|
||||
|
||||
@Test
|
||||
public void testBasicAuthenticationNoCreds() throws Exception {
|
||||
server.register("*", AsyncEchoHandler::new);
|
||||
@ -270,6 +285,69 @@ public void testBasicAuthenticationCredentialsCaching() throws Exception {
|
||||
Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicAuthenticationCredentialsCachingByPathPrefix() throws Exception {
|
||||
server.register("*", AsyncEchoHandler::new);
|
||||
|
||||
final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
|
||||
setTargetAuthenticationStrategy(authStrategy);
|
||||
final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
|
||||
addResponseInterceptor((response, entity, context)
|
||||
-> responseQueue.add(BasicResponseBuilder.copy(response).build()));
|
||||
|
||||
final HttpHost target = start();
|
||||
|
||||
final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
|
||||
.add(target, "test", "test".toCharArray())
|
||||
.build();
|
||||
|
||||
final AuthCache authCache = new BasicAuthCache();
|
||||
|
||||
for (final String requestPath: new String[] {"/blah/a", "/blah/b?huh", "/blah/c", "/bl%61h/%61"}) {
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
context.setAuthCache(authCache);
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
final Future<SimpleHttpResponse> future = httpclient.execute(SimpleRequestBuilder.get()
|
||||
.setHttpHost(target)
|
||||
.setPath(requestPath)
|
||||
.build(), context, null);
|
||||
final HttpResponse response = future.get();
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
|
||||
}
|
||||
|
||||
// There should be only single auth strategy call for all successful message exchanges
|
||||
Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
|
||||
MatcherAssert.assertThat(
|
||||
responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
|
||||
CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200)));
|
||||
|
||||
responseQueue.clear();
|
||||
authCache.clear();
|
||||
Mockito.reset(authStrategy);
|
||||
|
||||
for (final String requestPath: new String[] {"/blah/a", "/yada/a", "/blah/blah/"}) {
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
context.setAuthCache(authCache);
|
||||
final Future<SimpleHttpResponse> future = httpclient.execute(SimpleRequestBuilder.get()
|
||||
.setHttpHost(target)
|
||||
.setPath(requestPath)
|
||||
.build(), context, null);
|
||||
final HttpResponse response = future.get();
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
|
||||
}
|
||||
|
||||
// There should be an auth strategy call for all successful message exchanges
|
||||
Mockito.verify(authStrategy, Mockito.times(3)).select(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
|
||||
MatcherAssert.assertThat(
|
||||
responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
|
||||
CoreMatchers.equalTo(Arrays.asList(401, 200, 401, 200, 401, 200)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationUserinfoInRequestSuccess() throws Exception {
|
||||
server.register("*", AsyncEchoHandler::new);
|
||||
|
@ -38,6 +38,8 @@
|
||||
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
|
||||
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
|
||||
import org.apache.hc.client5.testing.SSLTestContexts;
|
||||
import org.apache.hc.core5.http.HttpRequestInterceptor;
|
||||
import org.apache.hc.core5.http.HttpResponseInterceptor;
|
||||
import org.apache.hc.core5.http.HttpVersion;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
import org.apache.hc.core5.http.config.Lookup;
|
||||
@ -92,6 +94,16 @@ void setTargetAuthenticationStrategy(final AuthenticationStrategy targetAuthStra
|
||||
clientBuilder.setTargetAuthenticationStrategy(targetAuthStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addResponseInterceptor(final HttpResponseInterceptor responseInterceptor) {
|
||||
clientBuilder.addResponseInterceptorLast(responseInterceptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addRequestInterceptor(final HttpRequestInterceptor requestInterceptor) {
|
||||
clientBuilder.addRequestInterceptorLast(requestInterceptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CloseableHttpAsyncClient createClient() throws Exception {
|
||||
return clientBuilder.build();
|
||||
|
@ -51,7 +51,9 @@
|
||||
import org.apache.hc.core5.http.HeaderElements;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequestInterceptor;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpResponseInterceptor;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.HttpVersion;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
@ -132,6 +134,16 @@ void setTargetAuthenticationStrategy(final AuthenticationStrategy targetAuthStra
|
||||
clientBuilder.setTargetAuthenticationStrategy(targetAuthStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addResponseInterceptor(final HttpResponseInterceptor responseInterceptor) {
|
||||
clientBuilder.addResponseInterceptorLast(responseInterceptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
void addRequestInterceptor(final HttpRequestInterceptor requestInterceptor) {
|
||||
clientBuilder.addRequestInterceptorLast(requestInterceptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CloseableHttpAsyncClient createClient() throws Exception {
|
||||
return clientBuilder.build();
|
||||
|
@ -30,8 +30,12 @@
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.hc.client5.http.auth.AuthCache;
|
||||
import org.apache.hc.client5.http.auth.AuthScheme;
|
||||
@ -63,6 +67,7 @@
|
||||
import org.apache.hc.core5.http.HttpException;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.config.Registry;
|
||||
import org.apache.hc.core5.http.config.RegistryBuilder;
|
||||
@ -74,7 +79,10 @@
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.http.protocol.HttpCoreContext;
|
||||
import org.apache.hc.core5.http.support.BasicResponseBuilder;
|
||||
import org.apache.hc.core5.net.URIAuthority;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
@ -267,6 +275,9 @@ public void testBasicAuthenticationCredentialsCaching() throws Exception {
|
||||
this.server.registerHandler("*", new EchoHandler());
|
||||
final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
|
||||
this.clientBuilder.setTargetAuthenticationStrategy(authStrategy);
|
||||
final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
|
||||
this.clientBuilder.addResponseInterceptorLast((response, entity, context)
|
||||
-> responseQueue.add(BasicResponseBuilder.copy(response).build()));
|
||||
|
||||
final HttpHost target = start();
|
||||
|
||||
@ -286,6 +297,69 @@ public void testBasicAuthenticationCredentialsCaching() throws Exception {
|
||||
}
|
||||
|
||||
Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
|
||||
MatcherAssert.assertThat(
|
||||
responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
|
||||
CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200, 200)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicAuthenticationCredentialsCachingByPathPrefix() throws Exception {
|
||||
this.server.registerHandler("*", new EchoHandler());
|
||||
final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
|
||||
this.clientBuilder.setTargetAuthenticationStrategy(authStrategy);
|
||||
final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
|
||||
this.clientBuilder.addResponseInterceptorLast((response, entity, context)
|
||||
-> responseQueue.add(BasicResponseBuilder.copy(response).build()));
|
||||
|
||||
final HttpHost target = start();
|
||||
|
||||
final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
|
||||
.add(target, "test", "test".toCharArray())
|
||||
.build();
|
||||
|
||||
final AuthCache authCache = new BasicAuthCache();
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
context.setAuthCache(authCache);
|
||||
context.setCredentialsProvider(credentialsProvider);
|
||||
|
||||
for (final String requestPath: new String[] {"/blah/a", "/blah/b?huh", "/blah/c", "/bl%61h/%61"}) {
|
||||
final HttpGet httpget = new HttpGet(requestPath);
|
||||
try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
|
||||
final HttpEntity entity1 = response.getEntity();
|
||||
Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
|
||||
Assert.assertNotNull(entity1);
|
||||
EntityUtils.consume(entity1);
|
||||
}
|
||||
}
|
||||
|
||||
// There should be only single auth strategy call for all successful message exchanges
|
||||
Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
|
||||
MatcherAssert.assertThat(
|
||||
responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
|
||||
CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200)));
|
||||
|
||||
responseQueue.clear();
|
||||
authCache.clear();
|
||||
Mockito.reset(authStrategy);
|
||||
|
||||
for (final String requestPath: new String[] {"/blah/a", "/yada/a", "/blah/blah/", "/buh/a"}) {
|
||||
final HttpGet httpget = new HttpGet(requestPath);
|
||||
try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
|
||||
final HttpEntity entity1 = response.getEntity();
|
||||
Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
|
||||
Assert.assertNotNull(entity1);
|
||||
EntityUtils.consume(entity1);
|
||||
}
|
||||
}
|
||||
|
||||
// There should be an auth strategy call for all successful message exchanges
|
||||
Mockito.verify(authStrategy, Mockito.times(2)).select(Mockito.any(), Mockito.any(), Mockito.any());
|
||||
|
||||
MatcherAssert.assertThat(
|
||||
responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
|
||||
CoreMatchers.equalTo(Arrays.asList(200, 401, 200, 200, 401, 200)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -29,6 +29,7 @@
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.net.NamedEndpoint;
|
||||
|
||||
/**
|
||||
* Strategy for default port resolution for protocol schemes.
|
||||
@ -43,4 +44,13 @@ public interface SchemePortResolver {
|
||||
*/
|
||||
int resolve(HttpHost host);
|
||||
|
||||
/**
|
||||
* Returns the actual port for the host based on the protocol scheme.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
default int resolve(String scheme, NamedEndpoint endpoint) {
|
||||
return resolve(new HttpHost(scheme, endpoint));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -36,12 +36,73 @@
|
||||
*/
|
||||
public interface AuthCache {
|
||||
|
||||
/**
|
||||
* Stores the authentication state with the given authentication scope in the cache.
|
||||
*
|
||||
* @param host the authentication authority.
|
||||
* @param authScheme the cacheable authentication state.
|
||||
*/
|
||||
void put(HttpHost host, AuthScheme authScheme);
|
||||
|
||||
/**
|
||||
* Returns the authentication state with the given authentication scope from the cache
|
||||
* if available.
|
||||
*
|
||||
* @param host the authentication authority.
|
||||
* @return the authentication state ir {@code null} if not available in the cache.
|
||||
*/
|
||||
AuthScheme get(HttpHost host);
|
||||
|
||||
/**
|
||||
* Removes the authentication state with the given authentication scope from the cache
|
||||
* if found.
|
||||
*
|
||||
* @param host the authentication authority.
|
||||
*/
|
||||
void remove(HttpHost host);
|
||||
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Stores the authentication state with the given authentication scope in the cache.
|
||||
*
|
||||
* @param host the authentication authority.
|
||||
* @param pathPrefix the path prefix (the path component up to the last segment separator).
|
||||
* Can be {@code null}.
|
||||
* @param authScheme the cacheable authentication state.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
default void put(HttpHost host, String pathPrefix, AuthScheme authScheme) {
|
||||
put(host, authScheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication state with the given authentication scope from the cache
|
||||
* if available.
|
||||
* @param host the authentication authority.
|
||||
* @param pathPrefix the path prefix (the path component up to the last segment separator).
|
||||
* Can be {@code null}.
|
||||
* @return the authentication state ir {@code null} if not available in the cache.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
default AuthScheme get(HttpHost host, String pathPrefix) {
|
||||
return get(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the authentication state with the given authentication scope from the cache
|
||||
* if found.
|
||||
*
|
||||
* @param host the authentication authority.
|
||||
* @param pathPrefix the path prefix (the path component up to the last segment separator).
|
||||
* Can be {@code null}.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
default void remove(HttpHost host, String pathPrefix) {
|
||||
remove(host);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ public enum State {
|
||||
private State state;
|
||||
private AuthScheme authScheme;
|
||||
private Queue<AuthScheme> authOptions;
|
||||
private String pathPrefix;
|
||||
|
||||
public AuthExchange() {
|
||||
super();
|
||||
@ -57,6 +58,7 @@ public void reset() {
|
||||
this.state = State.UNCHALLENGED;
|
||||
this.authOptions = null;
|
||||
this.authScheme = null;
|
||||
this.pathPrefix = null;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
@ -81,6 +83,20 @@ public boolean isConnectionBased() {
|
||||
return this.authScheme != null && this.authScheme.isConnectionBased();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public String getPathPrefix() {
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setPathPrefix(final String pathPrefix) {
|
||||
this.pathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the auth state with {@link AuthScheme} and clears auth options.
|
||||
*
|
||||
|
@ -39,8 +39,8 @@
|
||||
import org.apache.hc.client5.http.auth.AuthChallenge;
|
||||
import org.apache.hc.client5.http.auth.AuthScheme;
|
||||
import org.apache.hc.client5.http.auth.AuthSchemeFactory;
|
||||
import org.apache.hc.client5.http.auth.StandardAuthScheme;
|
||||
import org.apache.hc.client5.http.auth.ChallengeType;
|
||||
import org.apache.hc.client5.http.auth.StandardAuthScheme;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
@ -117,7 +117,7 @@ public List<AuthScheme> select(
|
||||
options.add(authScheme);
|
||||
} else {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("{}, Challenge for {} authentication scheme not available", exchangeId, schemeName);
|
||||
LOG.debug("{} Challenge for {} authentication scheme not available", exchangeId, schemeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
import org.apache.hc.core5.net.NamedEndpoint;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
|
||||
/**
|
||||
@ -46,14 +47,19 @@ public class DefaultSchemePortResolver implements SchemePortResolver {
|
||||
@Override
|
||||
public int resolve(final HttpHost host) {
|
||||
Args.notNull(host, "HTTP host");
|
||||
final int port = host.getPort();
|
||||
return resolve(host.getSchemeName(), host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int resolve(final String scheme, final NamedEndpoint endpoint) {
|
||||
Args.notNull(endpoint, "Endpoint");
|
||||
final int port = endpoint.getPort();
|
||||
if (port > 0) {
|
||||
return port;
|
||||
}
|
||||
final String name = host.getSchemeName();
|
||||
if (URIScheme.HTTP.same(name)) {
|
||||
if (URIScheme.HTTP.same(scheme)) {
|
||||
return 80;
|
||||
} else if (URIScheme.HTTPS.same(name)) {
|
||||
} else if (URIScheme.HTTPS.same(scheme)) {
|
||||
return 443;
|
||||
} else {
|
||||
return -1;
|
||||
|
@ -26,45 +26,48 @@
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.hc.core5.annotation.Internal;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
import org.apache.hc.core5.net.URIAuthority;
|
||||
import org.apache.hc.core5.net.PercentCodec;
|
||||
import org.apache.hc.core5.net.URIBuilder;
|
||||
|
||||
/**
|
||||
* Protocol support methods.
|
||||
* Protocol support methods. For internal use only.
|
||||
*
|
||||
* @since 5.1
|
||||
* @since 5.2
|
||||
*/
|
||||
@Internal
|
||||
public final class ProtocolSupport {
|
||||
public final class RequestSupport {
|
||||
|
||||
public static String getRequestUri(final HttpRequest request) {
|
||||
final URIAuthority authority = request.getAuthority();
|
||||
if (authority != null) {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
final String scheme = request.getScheme();
|
||||
buf.append(scheme != null ? scheme : URIScheme.HTTP.id);
|
||||
buf.append("://");
|
||||
if (authority.getUserInfo() != null) {
|
||||
buf.append(authority.getUserInfo());
|
||||
buf.append("@");
|
||||
public static String extractPathPrefix(final HttpRequest request) {
|
||||
final String path = request.getPath();
|
||||
try {
|
||||
final URIBuilder uriBuilder = new URIBuilder(path);
|
||||
uriBuilder.setFragment(null);
|
||||
uriBuilder.clearParameters();
|
||||
uriBuilder.normalizeSyntax();
|
||||
final List<String> pathSegments = uriBuilder.getPathSegments();
|
||||
|
||||
if (!pathSegments.isEmpty()) {
|
||||
pathSegments.remove(pathSegments.size() - 1);
|
||||
}
|
||||
buf.append(authority.getHostName());
|
||||
if (authority.getPort() != -1) {
|
||||
buf.append(":");
|
||||
buf.append(authority.getPort());
|
||||
if (pathSegments.isEmpty()) {
|
||||
return "/";
|
||||
} else {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
buf.append('/');
|
||||
for (final String pathSegment : pathSegments) {
|
||||
PercentCodec.encode(buf, pathSegment, StandardCharsets.US_ASCII);
|
||||
buf.append('/');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
final String path = request.getPath();
|
||||
if (path == null || !path.startsWith("/")) {
|
||||
buf.append("/");
|
||||
}
|
||||
if (path != null) {
|
||||
buf.append(path);
|
||||
}
|
||||
return buf.toString();
|
||||
} else {
|
||||
return request.getPath();
|
||||
} catch (final URISyntaxException ex) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
@ -379,7 +379,7 @@ private void createTunnel(
|
||||
final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, clientContext);
|
||||
authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, clientContext);
|
||||
}
|
||||
|
||||
final HttpRequest connect = new BasicHttpRequest(Method.CONNECT, nextHop, nextHop.toHostString());
|
||||
@ -444,9 +444,9 @@ private boolean needAuthentication(
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
if (proxyAuthRequested) {
|
||||
authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
|
||||
} else {
|
||||
authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,7 +455,7 @@ private boolean needAuthentication(
|
||||
proxyAuthStrategy, proxyAuthExchange, context);
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
|
||||
return updated;
|
||||
|
@ -43,6 +43,7 @@
|
||||
import org.apache.hc.client5.http.auth.CredentialsStore;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.AuthSupport;
|
||||
import org.apache.hc.client5.http.impl.RequestSupport;
|
||||
import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
|
||||
import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
@ -148,23 +149,36 @@ public void execute(
|
||||
}
|
||||
|
||||
final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
|
||||
final String pathPrefix = RequestSupport.extractPathPrefix(request);
|
||||
final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target);
|
||||
final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange();
|
||||
|
||||
if (!targetAuthExchange.isConnectionBased() &&
|
||||
targetAuthExchange.getPathPrefix() != null &&
|
||||
!pathPrefix.startsWith(targetAuthExchange.getPathPrefix())) {
|
||||
// force re-authentication if the current path prefix does not match
|
||||
// that of the previous authentication exchange.
|
||||
targetAuthExchange.reset();
|
||||
}
|
||||
if (targetAuthExchange.getPathPrefix() == null) {
|
||||
targetAuthExchange.setPathPrefix(pathPrefix);
|
||||
}
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.loadPreemptively(target, targetAuthExchange, clientContext);
|
||||
authCacheKeeper.loadPreemptively(target, pathPrefix, targetAuthExchange, clientContext);
|
||||
if (proxy != null) {
|
||||
authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, clientContext);
|
||||
authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, clientContext);
|
||||
}
|
||||
}
|
||||
|
||||
final AtomicBoolean challenged = new AtomicBoolean(false);
|
||||
internalExecute(target, targetAuthExchange, proxyAuthExchange,
|
||||
internalExecute(target, pathPrefix, targetAuthExchange, proxyAuthExchange,
|
||||
challenged, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
}
|
||||
|
||||
private void internalExecute(
|
||||
final HttpHost target,
|
||||
final String pathPrefix,
|
||||
final AuthExchange targetAuthExchange,
|
||||
final AuthExchange proxyAuthExchange,
|
||||
final AtomicBoolean challenged,
|
||||
@ -216,6 +230,7 @@ public AsyncDataConsumer handleResponse(
|
||||
proxyAuthExchange,
|
||||
proxy != null ? proxy : target,
|
||||
target,
|
||||
pathPrefix,
|
||||
response,
|
||||
clientContext)) {
|
||||
challenged.set(true);
|
||||
@ -267,7 +282,7 @@ public void completed() {
|
||||
if (entityProducer != null) {
|
||||
entityProducer.releaseResources();
|
||||
}
|
||||
internalExecute(target, targetAuthExchange, proxyAuthExchange,
|
||||
internalExecute(target, pathPrefix, targetAuthExchange, proxyAuthExchange,
|
||||
challenged, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
} catch (final HttpException | IOException ex) {
|
||||
asyncExecCallback.failed(ex);
|
||||
@ -298,6 +313,7 @@ private boolean needAuthentication(
|
||||
final AuthExchange proxyAuthExchange,
|
||||
final HttpHost proxy,
|
||||
final HttpHost target,
|
||||
final String pathPrefix,
|
||||
final HttpResponse response,
|
||||
final HttpClientContext context) {
|
||||
final RequestConfig config = context.getRequestConfig();
|
||||
@ -307,9 +323,9 @@ private boolean needAuthentication(
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
if (targetAuthRequested) {
|
||||
authCacheKeeper.updateOnChallenge(target, targetAuthExchange, context);
|
||||
authCacheKeeper.updateOnChallenge(target, pathPrefix, targetAuthExchange, context);
|
||||
} else {
|
||||
authCacheKeeper.updateOnNoChallenge(target, targetAuthExchange, context);
|
||||
authCacheKeeper.updateOnNoChallenge(target, pathPrefix, targetAuthExchange, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,9 +334,9 @@ private boolean needAuthentication(
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
if (proxyAuthRequested) {
|
||||
authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
|
||||
} else {
|
||||
authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,7 +345,7 @@ private boolean needAuthentication(
|
||||
targetAuthStrategy, targetAuthExchange, context);
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.updateOnResponse(target, targetAuthExchange, context);
|
||||
authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
|
||||
}
|
||||
|
||||
return updated;
|
||||
@ -339,7 +355,7 @@ private boolean needAuthentication(
|
||||
proxyAuthStrategy, proxyAuthExchange, context);
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
|
||||
return updated;
|
||||
|
@ -59,32 +59,39 @@ public AuthCacheKeeper(final SchemePortResolver schemePortResolver) {
|
||||
}
|
||||
|
||||
public void updateOnChallenge(final HttpHost host,
|
||||
final String pathPrefix,
|
||||
final AuthExchange authExchange,
|
||||
final HttpContext context) {
|
||||
clearCache(host, HttpClientContext.adapt(context));
|
||||
clearCache(host, pathPrefix, HttpClientContext.adapt(context));
|
||||
}
|
||||
|
||||
public void updateOnNoChallenge(final HttpHost host,
|
||||
final String pathPrefix,
|
||||
final AuthExchange authExchange,
|
||||
final HttpContext context) {
|
||||
if (authExchange.getState() == AuthExchange.State.SUCCESS) {
|
||||
updateCache(host, authExchange.getAuthScheme(), HttpClientContext.adapt(context));
|
||||
updateCache(host, pathPrefix, authExchange.getAuthScheme(), HttpClientContext.adapt(context));
|
||||
}
|
||||
}
|
||||
|
||||
public void updateOnResponse(final HttpHost host,
|
||||
final String pathPrefix,
|
||||
final AuthExchange authExchange,
|
||||
final HttpContext context) {
|
||||
if (authExchange.getState() == AuthExchange.State.FAILURE) {
|
||||
clearCache(host, HttpClientContext.adapt(context));
|
||||
clearCache(host, pathPrefix, HttpClientContext.adapt(context));
|
||||
}
|
||||
}
|
||||
|
||||
public void loadPreemptively(final HttpHost host,
|
||||
final String pathPrefix,
|
||||
final AuthExchange authExchange,
|
||||
final HttpContext context) {
|
||||
if (authExchange.getState() == AuthExchange.State.UNCHALLENGED) {
|
||||
final AuthScheme authScheme = loadFromCache(host, HttpClientContext.adapt(context));
|
||||
AuthScheme authScheme = loadFromCache(host, pathPrefix, HttpClientContext.adapt(context));
|
||||
if (authScheme == null && pathPrefix != null) {
|
||||
authScheme = loadFromCache(host, null, HttpClientContext.adapt(context));
|
||||
}
|
||||
if (authScheme != null) {
|
||||
authExchange.select(authScheme);
|
||||
}
|
||||
@ -92,14 +99,16 @@ public void loadPreemptively(final HttpHost host,
|
||||
}
|
||||
|
||||
private AuthScheme loadFromCache(final HttpHost host,
|
||||
final String pathPrefix,
|
||||
final HttpClientContext clientContext) {
|
||||
final AuthCache authCache = clientContext.getAuthCache();
|
||||
if (authCache != null) {
|
||||
final AuthScheme authScheme = authCache.get(host);
|
||||
final AuthScheme authScheme = authCache.get(host, pathPrefix);
|
||||
if (authScheme != null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
final String exchangeId = clientContext.getExchangeId();
|
||||
LOG.debug("{} Re-using cached '{}' auth scheme for {}", exchangeId, authScheme.getName(), host);
|
||||
LOG.debug("{} Re-using cached '{}' auth scheme for {}{}", exchangeId, authScheme.getName(), host,
|
||||
pathPrefix != null ? pathPrefix : "");
|
||||
}
|
||||
return authScheme;
|
||||
}
|
||||
@ -108,6 +117,7 @@ private AuthScheme loadFromCache(final HttpHost host,
|
||||
}
|
||||
|
||||
private void updateCache(final HttpHost host,
|
||||
final String pathPrefix,
|
||||
final AuthScheme authScheme,
|
||||
final HttpClientContext clientContext) {
|
||||
final boolean cacheable = authScheme.getClass().getAnnotation(AuthStateCacheable.class) != null;
|
||||
@ -119,21 +129,24 @@ private void updateCache(final HttpHost host,
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
final String exchangeId = clientContext.getExchangeId();
|
||||
LOG.debug("{} Caching '{}' auth scheme for {}", exchangeId, authScheme.getName(), host);
|
||||
LOG.debug("{} Caching '{}' auth scheme for {}{}", exchangeId, authScheme.getName(), host,
|
||||
pathPrefix != null ? pathPrefix : "");
|
||||
}
|
||||
authCache.put(host, authScheme);
|
||||
authCache.put(host, pathPrefix, authScheme);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearCache(final HttpHost host,
|
||||
final String pathPrefix,
|
||||
final HttpClientContext clientContext) {
|
||||
final AuthCache authCache = clientContext.getAuthCache();
|
||||
if (authCache != null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
final String exchangeId = clientContext.getExchangeId();
|
||||
LOG.debug("{} Clearing cached auth scheme for {}", exchangeId, host);
|
||||
LOG.debug("{} Clearing cached auth scheme for {}{}", exchangeId, host,
|
||||
pathPrefix != null ? pathPrefix : "");
|
||||
}
|
||||
authCache.remove(host);
|
||||
authCache.remove(host, pathPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ -39,11 +40,12 @@
|
||||
import org.apache.hc.client5.http.auth.AuthCache;
|
||||
import org.apache.hc.client5.http.auth.AuthScheme;
|
||||
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
|
||||
import org.apache.hc.client5.http.routing.RoutingSupport;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.net.NamedEndpoint;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
import org.apache.hc.core5.util.LangUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -62,7 +64,65 @@ public class BasicAuthCache implements AuthCache {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthCache.class);
|
||||
|
||||
private final Map<HttpHost, byte[]> map;
|
||||
static class Key {
|
||||
|
||||
final String scheme;
|
||||
final String host;
|
||||
final int port;
|
||||
final String pathPrefix;
|
||||
|
||||
Key(final String scheme, final String host, final int port, final String pathPrefix) {
|
||||
Args.notBlank(scheme, "Scheme");
|
||||
Args.notBlank(host, "Scheme");
|
||||
this.scheme = scheme.toLowerCase(Locale.ROOT);
|
||||
this.host = host.toLowerCase(Locale.ROOT);
|
||||
this.port = port;
|
||||
this.pathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof Key) {
|
||||
final Key that = (Key) obj;
|
||||
return this.scheme.equals(that.scheme) &&
|
||||
this.host.equals(that.host) &&
|
||||
this.port == that.port &&
|
||||
LangUtils.equals(this.pathPrefix, that.pathPrefix);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = LangUtils.HASH_SEED;
|
||||
hash = LangUtils.hashCode(hash, this.scheme);
|
||||
hash = LangUtils.hashCode(hash, this.host);
|
||||
hash = LangUtils.hashCode(hash, this.port);
|
||||
hash = LangUtils.hashCode(hash, this.pathPrefix);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
buf.append(scheme).append("://").append(host);
|
||||
if (port >= 0) {
|
||||
buf.append(":").append(port);
|
||||
}
|
||||
if (pathPrefix != null) {
|
||||
if (!pathPrefix.startsWith("/")) {
|
||||
buf.append("/");
|
||||
}
|
||||
buf.append(pathPrefix);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<Key, byte[]> map;
|
||||
private final SchemePortResolver schemePortResolver;
|
||||
|
||||
/**
|
||||
@ -80,8 +140,27 @@ public BasicAuthCache() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
private Key key(final String scheme, final NamedEndpoint authority, final String pathPrefix) {
|
||||
return new Key(scheme, authority.getHostName(), schemePortResolver.resolve(scheme, authority), pathPrefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final HttpHost host, final AuthScheme authScheme) {
|
||||
put(host, null, authScheme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthScheme get(final HttpHost host) {
|
||||
return get(host, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final HttpHost host) {
|
||||
remove(host, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(final HttpHost host, final String pathPrefix, final AuthScheme authScheme) {
|
||||
Args.notNull(host, "HTTP host");
|
||||
if (authScheme == null) {
|
||||
return;
|
||||
@ -92,8 +171,7 @@ public void put(final HttpHost host, final AuthScheme authScheme) {
|
||||
try (final ObjectOutputStream out = new ObjectOutputStream(buf)) {
|
||||
out.writeObject(authScheme);
|
||||
}
|
||||
final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
|
||||
this.map.put(key, buf.toByteArray());
|
||||
this.map.put(key(host.getSchemeName(), host, pathPrefix), buf.toByteArray());
|
||||
} catch (final IOException ex) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Unexpected I/O error while serializing auth scheme", ex);
|
||||
@ -107,10 +185,9 @@ public void put(final HttpHost host, final AuthScheme authScheme) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthScheme get(final HttpHost host) {
|
||||
public AuthScheme get(final HttpHost host, final String pathPrefix) {
|
||||
Args.notNull(host, "HTTP host");
|
||||
final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
|
||||
final byte[] bytes = this.map.get(key);
|
||||
final byte[] bytes = this.map.get(key(host.getSchemeName(), host, pathPrefix));
|
||||
if (bytes != null) {
|
||||
try {
|
||||
final ByteArrayInputStream buf = new ByteArrayInputStream(bytes);
|
||||
@ -131,10 +208,9 @@ public AuthScheme get(final HttpHost host) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(final HttpHost host) {
|
||||
public void remove(final HttpHost host, final String pathPrefix) {
|
||||
Args.notNull(host, "HTTP host");
|
||||
final HttpHost key = RoutingSupport.normalize(host, schemePortResolver);
|
||||
this.map.remove(key);
|
||||
this.map.remove(key(host.getSchemeName(), host, pathPrefix));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -215,7 +215,7 @@ private boolean createTunnelToTarget(
|
||||
final AuthExchange proxyAuthExchange = context.getAuthExchange(proxy);
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
|
||||
ClassicHttpResponse response = null;
|
||||
@ -254,9 +254,9 @@ private boolean createTunnelToTarget(
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
if (proxyAuthRequested) {
|
||||
authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
|
||||
} else {
|
||||
authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,7 +265,7 @@ private boolean createTunnelToTarget(
|
||||
proxyAuthStrategy, proxyAuthExchange, context);
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
if (updated) {
|
||||
// Retry request
|
||||
|
@ -42,6 +42,7 @@
|
||||
import org.apache.hc.client5.http.classic.ExecRuntime;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.AuthSupport;
|
||||
import org.apache.hc.client5.http.impl.RequestSupport;
|
||||
import org.apache.hc.client5.http.impl.auth.AuthCacheKeeper;
|
||||
import org.apache.hc.client5.http.impl.auth.HttpAuthenticator;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
@ -151,14 +152,26 @@ public ClassicHttpResponse execute(
|
||||
}
|
||||
|
||||
final HttpHost target = new HttpHost(request.getScheme(), request.getAuthority());
|
||||
final String pathPrefix = RequestSupport.extractPathPrefix(request);
|
||||
|
||||
final AuthExchange targetAuthExchange = context.getAuthExchange(target);
|
||||
final AuthExchange proxyAuthExchange = proxy != null ? context.getAuthExchange(proxy) : new AuthExchange();
|
||||
|
||||
if (!targetAuthExchange.isConnectionBased() &&
|
||||
targetAuthExchange.getPathPrefix() != null &&
|
||||
!pathPrefix.startsWith(targetAuthExchange.getPathPrefix())) {
|
||||
// force re-authentication if the current path prefix does not match
|
||||
// that of the previous authentication exchange.
|
||||
targetAuthExchange.reset();
|
||||
}
|
||||
if (targetAuthExchange.getPathPrefix() == null) {
|
||||
targetAuthExchange.setPathPrefix(pathPrefix);
|
||||
}
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.loadPreemptively(target, targetAuthExchange, context);
|
||||
authCacheKeeper.loadPreemptively(target, pathPrefix, targetAuthExchange, context);
|
||||
if (proxy != null) {
|
||||
authCacheKeeper.loadPreemptively(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.loadPreemptively(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,6 +219,7 @@ public ClassicHttpResponse execute(
|
||||
proxyAuthExchange,
|
||||
proxy != null ? proxy : target,
|
||||
target,
|
||||
pathPrefix,
|
||||
response,
|
||||
context)) {
|
||||
// Make sure the response body is fully consumed, if present
|
||||
@ -259,6 +273,7 @@ private boolean needAuthentication(
|
||||
final AuthExchange proxyAuthExchange,
|
||||
final HttpHost proxy,
|
||||
final HttpHost target,
|
||||
final String pathPrefix,
|
||||
final HttpResponse response,
|
||||
final HttpClientContext context) {
|
||||
final RequestConfig config = context.getRequestConfig();
|
||||
@ -268,9 +283,9 @@ private boolean needAuthentication(
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
if (targetAuthRequested) {
|
||||
authCacheKeeper.updateOnChallenge(target, targetAuthExchange, context);
|
||||
authCacheKeeper.updateOnChallenge(target, pathPrefix, targetAuthExchange, context);
|
||||
} else {
|
||||
authCacheKeeper.updateOnNoChallenge(target, targetAuthExchange, context);
|
||||
authCacheKeeper.updateOnNoChallenge(target, pathPrefix, targetAuthExchange, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,9 +294,9 @@ private boolean needAuthentication(
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
if (proxyAuthRequested) {
|
||||
authCacheKeeper.updateOnChallenge(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnChallenge(proxy, null, proxyAuthExchange, context);
|
||||
} else {
|
||||
authCacheKeeper.updateOnNoChallenge(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnNoChallenge(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,7 +305,7 @@ private boolean needAuthentication(
|
||||
targetAuthStrategy, targetAuthExchange, context);
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.updateOnResponse(target, targetAuthExchange, context);
|
||||
authCacheKeeper.updateOnResponse(target, pathPrefix, targetAuthExchange, context);
|
||||
}
|
||||
|
||||
return updated;
|
||||
@ -300,7 +315,7 @@ private boolean needAuthentication(
|
||||
proxyAuthStrategy, proxyAuthExchange, context);
|
||||
|
||||
if (authCacheKeeper != null) {
|
||||
authCacheKeeper.updateOnResponse(proxy, proxyAuthExchange, context);
|
||||
authCacheKeeper.updateOnResponse(proxy, null, proxyAuthExchange, context);
|
||||
}
|
||||
|
||||
return updated;
|
||||
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl;
|
||||
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.Method;
|
||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
||||
import org.apache.hc.core5.net.URIAuthority;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Simple tests for {@link ProtocolSupport}.
|
||||
*/
|
||||
public class TestProtocolSupport {
|
||||
|
||||
@Test
|
||||
public void testGetRequestUri() {
|
||||
final HttpRequest request = new BasicHttpRequest(Method.GET, "");
|
||||
MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("/"));
|
||||
request.setAuthority(new URIAuthority("testUser", "localhost", 8080));
|
||||
MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("http://testUser@localhost:8080/"));
|
||||
request.setScheme("https");
|
||||
MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("https://testUser@localhost:8080/"));
|
||||
request.setPath("blah");
|
||||
MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("https://testUser@localhost:8080/blah"));
|
||||
request.setPath("/blah/blah");
|
||||
MatcherAssert.assertThat(ProtocolSupport.getRequestUri(request), CoreMatchers.equalTo("https://testUser@localhost:8080/blah/blah"));
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl;
|
||||
|
||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Simple tests for {@link RequestSupport}.
|
||||
*/
|
||||
public class TestRequestSupport {
|
||||
|
||||
@Test
|
||||
public void testPathPrefixExtraction() {
|
||||
Assert.assertEquals("/aaaa/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/bbbb")));
|
||||
Assert.assertEquals("/aaaa/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/")));
|
||||
Assert.assertEquals("/aaaa/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/../aaaa/")));
|
||||
Assert.assertEquals("/aaaa/bbbb/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/bbbb/cccc")));
|
||||
Assert.assertEquals("/aaaa/bbbb/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/bbbb/")));
|
||||
Assert.assertEquals("/aaaa/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa/bbbb?////")));
|
||||
Assert.assertEquals("/aa%2Faa/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aa%2faa/bbbb")));
|
||||
Assert.assertEquals("/aa%2Faa/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/a%61%2fa%61/bbbb")));
|
||||
Assert.assertEquals("/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/")));
|
||||
Assert.assertEquals("/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "/aaaa")));
|
||||
Assert.assertEquals("/", RequestSupport.extractPathPrefix(new BasicHttpRequest("GET", "")));
|
||||
}
|
||||
|
||||
}
|
@ -67,7 +67,7 @@ public void testNullAuthScheme() throws Exception {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreNonserializable() throws Exception {
|
||||
public void testStoreNonSerializable() throws Exception {
|
||||
final BasicAuthCache cache = new BasicAuthCache();
|
||||
final AuthScheme authScheme = new NTLMScheme();
|
||||
cache.put(new HttpHost("localhost", 80), authScheme);
|
||||
|
@ -1,188 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.auth;
|
||||
|
||||
import org.apache.hc.client5.http.HttpRoute;
|
||||
import org.apache.hc.client5.http.auth.AuthCache;
|
||||
import org.apache.hc.client5.http.auth.AuthExchange;
|
||||
import org.apache.hc.client5.http.auth.AuthScope;
|
||||
import org.apache.hc.client5.http.auth.Credentials;
|
||||
import org.apache.hc.client5.http.auth.CredentialsProvider;
|
||||
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.client5.http.protocol.RequestAuthCache;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpRequestInterceptor;
|
||||
import org.apache.hc.core5.http.message.BasicHttpRequest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestRequestAuthCache {
|
||||
|
||||
private HttpHost target;
|
||||
private HttpHost proxy;
|
||||
private Credentials creds1;
|
||||
private Credentials creds2;
|
||||
private AuthScope authscope1;
|
||||
private AuthScope authscope2;
|
||||
private BasicScheme authscheme1;
|
||||
private BasicScheme authscheme2;
|
||||
private CredentialsProvider credProvider;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.target = new HttpHost("localhost", 80);
|
||||
this.proxy = new HttpHost("localhost", 8080);
|
||||
|
||||
this.creds1 = new UsernamePasswordCredentials("user1", "secret1".toCharArray());
|
||||
this.creds2 = new UsernamePasswordCredentials("user2", "secret2".toCharArray());
|
||||
this.authscope1 = new AuthScope(this.target);
|
||||
this.authscope2 = new AuthScope(this.proxy);
|
||||
this.authscheme1 = new BasicScheme();
|
||||
this.authscheme2 = new BasicScheme();
|
||||
|
||||
this.credProvider = CredentialsProviderBuilder.create()
|
||||
.add(this.authscope1, this.creds1)
|
||||
.add(this.authscope2, this.creds2)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestParameterCheck() throws Exception {
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
final HttpRequestInterceptor interceptor = new RequestAuthCache();
|
||||
Assert.assertThrows(NullPointerException.class, () ->
|
||||
interceptor.process(null, null, context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextParameterCheck() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
final HttpRequestInterceptor interceptor = new RequestAuthCache();
|
||||
Assert.assertThrows(NullPointerException.class, () ->
|
||||
interceptor.process(request, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreemptiveTargetAndProxyAuth() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credProvider);
|
||||
context.setAttribute(HttpClientContext.HTTP_ROUTE, new HttpRoute(this.target, null, this.proxy, false));
|
||||
|
||||
final AuthCache authCache = new BasicAuthCache();
|
||||
authCache.put(this.target, this.authscheme1);
|
||||
authCache.put(this.proxy, this.authscheme2);
|
||||
|
||||
context.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
|
||||
|
||||
final HttpRequestInterceptor interceptor = new RequestAuthCache();
|
||||
interceptor.process(request, null, context);
|
||||
|
||||
final AuthExchange targetAuthExchange = context.getAuthExchange(this.target);
|
||||
final AuthExchange proxyAuthExchange = context.getAuthExchange(this.proxy);
|
||||
|
||||
Assert.assertNotNull(targetAuthExchange);
|
||||
Assert.assertNotNull(targetAuthExchange.getAuthScheme());
|
||||
Assert.assertNotNull(proxyAuthExchange);
|
||||
Assert.assertNotNull(proxyAuthExchange.getAuthScheme());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCredentialsProviderNotSet() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
context.setAttribute(HttpClientContext.CREDS_PROVIDER, null);
|
||||
context.setAttribute(HttpClientContext.HTTP_ROUTE, new HttpRoute(this.target, null, this.proxy, false));
|
||||
|
||||
final AuthCache authCache = new BasicAuthCache();
|
||||
authCache.put(this.target, this.authscheme1);
|
||||
authCache.put(this.proxy, this.authscheme2);
|
||||
|
||||
context.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
|
||||
|
||||
final HttpRequestInterceptor interceptor = new RequestAuthCache();
|
||||
interceptor.process(request, null, context);
|
||||
|
||||
final AuthExchange targetAuthExchange = context.getAuthExchange(this.target);
|
||||
final AuthExchange proxyAuthExchange = context.getAuthExchange(this.proxy);
|
||||
|
||||
Assert.assertNotNull(targetAuthExchange);
|
||||
Assert.assertNull(targetAuthExchange.getAuthScheme());
|
||||
Assert.assertNotNull(proxyAuthExchange);
|
||||
Assert.assertNull(proxyAuthExchange.getAuthScheme());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthCacheNotSet() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credProvider);
|
||||
context.setAttribute(HttpClientContext.HTTP_ROUTE, new HttpRoute(this.target, null, this.proxy, false));
|
||||
context.setAttribute(HttpClientContext.AUTH_CACHE, null);
|
||||
|
||||
final HttpRequestInterceptor interceptor = new RequestAuthCache();
|
||||
interceptor.process(request, null, context);
|
||||
|
||||
final AuthExchange targetAuthExchange = context.getAuthExchange(this.target);
|
||||
final AuthExchange proxyAuthExchange = context.getAuthExchange(this.proxy);
|
||||
|
||||
Assert.assertNotNull(targetAuthExchange);
|
||||
Assert.assertNull(targetAuthExchange.getAuthScheme());
|
||||
Assert.assertNotNull(proxyAuthExchange);
|
||||
Assert.assertNull(proxyAuthExchange.getAuthScheme());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthCacheEmpty() throws Exception {
|
||||
final HttpRequest request = new BasicHttpRequest("GET", "/");
|
||||
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credProvider);
|
||||
context.setAttribute(HttpClientContext.HTTP_ROUTE, new HttpRoute(this.target, null, this.proxy, false));
|
||||
|
||||
final AuthCache authCache = new BasicAuthCache();
|
||||
context.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
|
||||
|
||||
final HttpRequestInterceptor interceptor = new RequestAuthCache();
|
||||
interceptor.process(request, null, context);
|
||||
|
||||
final AuthExchange targetAuthExchange = context.getAuthExchange(this.target);
|
||||
final AuthExchange proxyAuthExchange = context.getAuthExchange(this.proxy);
|
||||
|
||||
Assert.assertNotNull(targetAuthExchange);
|
||||
Assert.assertNull(targetAuthExchange.getAuthScheme());
|
||||
Assert.assertNotNull(proxyAuthExchange);
|
||||
Assert.assertNull(proxyAuthExchange.getAuthScheme());
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user