From d5039137ca7ec49052748e10ec64318bb2967938 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Tue, 2 May 2017 18:22:59 +0000 Subject: [PATCH] Improved protocol handling in the async request execution chain; implemented cookie processing and authentication handling git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1793567 13f79535-47bb-0310-9956-ffa450edef68 --- .../testing/async/IntegrationTestBase.java | 11 +- .../testing/async/TestAsyncRedirects.java | 3 +- .../async/TestClientAuthentication.java | 627 ++++++++++++++++++ .../examples/AsyncClientAuthentication.java | 88 +++ .../AuthSupport.java} | 23 +- .../http/impl/async/AsyncProtocolExec.java | 179 ++++- .../impl/async/HttpAsyncClientBuilder.java | 163 ++++- .../impl/async/InternalHttpAsyncClient.java | 29 + .../client5/http/impl/sync/ProtocolExec.java | 14 +- 9 files changed, 1112 insertions(+), 25 deletions(-) create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java create mode 100644 httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientAuthentication.java rename httpclient5/src/main/java/org/apache/hc/client5/http/{auth/util/CredentialSupport.java => impl/AuthSupport.java} (74%) diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java index e2701b189..262eb8243 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java @@ -33,6 +33,9 @@ import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; +import org.apache.hc.core5.http.config.H1Config; +import org.apache.hc.core5.http.impl.HttpProcessors; +import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.io.ShutdownType; import org.apache.hc.core5.reactor.ListenerEndpoint; import org.junit.Rule; @@ -69,8 +72,8 @@ public abstract class IntegrationTestBase extends LocalAsyncServerTestBase { }; - public HttpHost start() throws Exception { - server.start(); + public HttpHost start(final HttpProcessor httpProcessor, final H1Config h1Config) throws Exception { + server.start(httpProcessor, h1Config); final ListenerEndpoint listener = server.listen(new InetSocketAddress(0)); httpclient = clientBuilder.build(); httpclient.start(); @@ -79,4 +82,8 @@ public abstract class IntegrationTestBase extends LocalAsyncServerTestBase { return new HttpHost("localhost", address.getPort(), scheme.name()); } + public HttpHost start() throws Exception { + return start(HttpProcessors.server(), H1Config.DEFAULT); + } + } diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java index 677a2b8ad..27866ba8f 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java @@ -70,7 +70,6 @@ import org.apache.hc.core5.reactor.ListenerEndpoint; import org.apache.hc.core5.testing.nio.Http2TestServer; import org.apache.hc.core5.util.TimeValue; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -641,7 +640,7 @@ public class TestAsyncRedirects extends IntegrationTestBase { } } - @Test @Ignore + @Test public void testRedirectWithCookie() throws Exception { server.register("*", new Supplier() { diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java new file mode 100644 index 000000000..a1d258805 --- /dev/null +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java @@ -0,0 +1,627 @@ +/* + * ==================================================================== + * 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 + * . + * + */ +package org.apache.hc.client5.testing.async; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicLong; + +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.SimpleRequestProducer; +import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer; +import org.apache.hc.client5.http.auth.AuthChallenge; +import org.apache.hc.client5.http.auth.AuthScheme; +import org.apache.hc.client5.http.auth.AuthSchemeProvider; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.ChallengeType; +import org.apache.hc.client5.http.auth.Credentials; +import org.apache.hc.client5.http.auth.CredentialsStore; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.auth.BasicScheme; +import org.apache.hc.client5.http.impl.protocol.DefaultAuthenticationStrategy; +import org.apache.hc.client5.http.impl.sync.BasicCredentialsProvider; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.sync.methods.HttpGet; +import org.apache.hc.client5.testing.auth.RequestBasicAuth; +import org.apache.hc.client5.testing.auth.ResponseBasicUnauthorized; +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HeaderElements; +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.HttpRequest; +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.URIScheme; +import org.apache.hc.core5.http.config.H1Config; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.impl.HttpProcessors; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.http.message.BasicHttpResponse; +import org.apache.hc.core5.http.nio.AsyncResponseProducer; +import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler; +import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer; +import org.apache.hc.core5.http.nio.support.BasicAsyncResponseProducer; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class TestClientAuthentication extends IntegrationTestBase { + + @Parameterized.Parameters(name = "{0}") + public static Collection protocols() { + return Arrays.asList(new Object[][]{ + {URIScheme.HTTP}, + {URIScheme.HTTPS}, + }); + } + + public TestClientAuthentication(final URIScheme scheme) { + super(scheme); + } + + @Override + public HttpHost start() throws Exception { + return super.start(HttpProcessors.customServer(null) + .add(new RequestBasicAuth()) + .add(new ResponseBasicUnauthorized()) + .build(), + H1Config.DEFAULT); + } + + static class AuthHandler extends AbstractSimpleServerExchangeHandler { + + private final boolean keepAlive; + + AuthHandler(final boolean keepAlive) { + super(); + this.keepAlive = keepAlive; + } + + AuthHandler() { + this(true); + } + + @Override + protected SimpleHttpResponse handle( + final SimpleHttpRequest request, + final HttpCoreContext context) throws HttpException { + final String creds = (String) context.getAttribute("creds"); + final SimpleHttpResponse response; + if (creds == null || !creds.equals("test:test")) { + response = new SimpleHttpResponse(HttpStatus.SC_UNAUTHORIZED); + } else { + response = new SimpleHttpResponse(HttpStatus.SC_OK, "success", ContentType.TEXT_PLAIN); + } + response.setHeader(HttpHeaders.CONNECTION, this.keepAlive ? HeaderElements.KEEP_ALIVE : HeaderElements.CLOSE); + return response; + } + + } + + static class TestCredentialsProvider implements CredentialsStore { + + private final Credentials creds; + private AuthScope authscope; + + TestCredentialsProvider(final Credentials creds) { + super(); + this.creds = creds; + } + + @Override + public void clear() { + } + + @Override + public Credentials getCredentials(final AuthScope authscope, final HttpContext context) { + this.authscope = authscope; + return this.creds; + } + + @Override + public void setCredentials(final AuthScope authscope, final Credentials credentials) { + } + + public AuthScope getAuthScope() { + return this.authscope; + } + + } + + @Test + public void testBasicAuthenticationNoCreds() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler(); + } + + }); + final HttpHost target = start(); + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider(null); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); + final HttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode()); + final AuthScope authscope = credsProvider.getAuthScope(); + Assert.assertNotNull(authscope); + Assert.assertEquals("test realm", authscope.getRealm()); + } + + @Test + public void testBasicAuthenticationFailure() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler(); + } + + }); + final HttpHost target = start(); + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider( + new UsernamePasswordCredentials("test", "all-wrong".toCharArray())); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); + final HttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode()); + final AuthScope authscope = credsProvider.getAuthScope(); + Assert.assertNotNull(authscope); + Assert.assertEquals("test realm", authscope.getRealm()); + } + + @Test + public void testBasicAuthenticationSuccess() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler(); + } + + }); + final HttpHost target = start(); + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider( + new UsernamePasswordCredentials("test", "test".toCharArray())); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); + final HttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + final AuthScope authscope = credsProvider.getAuthScope(); + Assert.assertNotNull(authscope); + Assert.assertEquals("test realm", authscope.getRealm()); + } + + @Test + public void testBasicAuthenticationSuccessNonPersistentConnection() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler(false); + } + + }); + final HttpHost target = start(); + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider( + new UsernamePasswordCredentials("test", "test".toCharArray())); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); + final HttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + final AuthScope authscope = credsProvider.getAuthScope(); + Assert.assertNotNull(authscope); + Assert.assertEquals("test realm", authscope.getRealm()); + } + + @Test + public void testBasicAuthenticationExpectationFailure() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler() { + + @Override + protected AsyncResponseProducer verify( + final HttpRequest request, + final HttpContext context) throws IOException, HttpException { + + final String creds = (String) context.getAttribute("creds"); + if (creds == null || !creds.equals("test:test")) { + return new BasicAsyncResponseProducer(new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED), + new StringAsyncEntityProducer("Unauthorized")); + } else { + return null; + } + } + + }; + } + + }); + final HttpHost target = start(); + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider( + new UsernamePasswordCredentials("test", "all-wrong".toCharArray())); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future = httpclient.execute( + SimpleHttpRequest.put(target, "/", "Some important stuff", ContentType.TEXT_PLAIN), context, null); + final HttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode()); + } + + @Test + public void testBasicAuthenticationExpectationSuccess() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler() { + + @Override + protected AsyncResponseProducer verify( + final HttpRequest request, + final HttpContext context) throws IOException, HttpException { + + final String creds = (String) context.getAttribute("creds"); + if (creds == null || !creds.equals("test:test")) { + return new BasicAsyncResponseProducer(new BasicHttpResponse(HttpStatus.SC_UNAUTHORIZED), + new StringAsyncEntityProducer("Unauthorized")); + } else { + return null; + } + } + + }; + } + + }); + final HttpHost target = start(); + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider( + new UsernamePasswordCredentials("test", "test".toCharArray())); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future = httpclient.execute( + SimpleHttpRequest.put(target, "/", "Some important stuff", ContentType.TEXT_PLAIN), context, null); + final HttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + final AuthScope authscope = credsProvider.getAuthScope(); + Assert.assertNotNull(authscope); + Assert.assertEquals("test realm", authscope.getRealm()); + } + + @Test + public void testBasicAuthenticationCredentialsCaching() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler(); + } + + }); + + final AtomicLong count = new AtomicLong(0); + this.clientBuilder.setTargetAuthenticationStrategy(new DefaultAuthenticationStrategy() { + + @Override + public List select( + final ChallengeType challengeType, + final Map challenges, + final HttpContext context) { + count.incrementAndGet(); + return super.select(challengeType, challenges, context); + } + }); + final HttpHost target = start(); + + final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(AuthScope.ANY, + new UsernamePasswordCredentials("test", "test".toCharArray())); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future1 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); + final HttpResponse response1 = future1.get(); + Assert.assertNotNull(response1); + Assert.assertEquals(HttpStatus.SC_OK, response1.getCode()); + + final Future future2 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); + final HttpResponse response2 = future2.get(); + Assert.assertNotNull(response2); + Assert.assertEquals(HttpStatus.SC_OK, response2.getCode()); + + Assert.assertEquals(1, count.get()); + } + + @Test + public void testAuthenticationUserinfoInRequestSuccess() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler(); + } + + }); + final HttpHost target = start(); + + final HttpClientContext context = HttpClientContext.create(); + final Future future = httpclient.execute( + SimpleHttpRequest.get(target.getSchemeName() + "://test:test@" + target.toHostString() + "/"), context, null); + final SimpleHttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + Assert.assertEquals("success", response.getBody()); + } + + @Test + public void testAuthenticationUserinfoInRequestFailure() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler(); + } + + }); + final HttpHost target = start(); + + final HttpClientContext context = HttpClientContext.create(); + final Future future = httpclient.execute( + SimpleHttpRequest.get(target.getSchemeName() + "://test:all-worng@" + target.toHostString() + "/"), context, null); + final SimpleHttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode()); + } + + @Test + public void testAuthenticationUserinfoInRedirectSuccess() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AuthHandler(); + } + + }); + final HttpHost target = start(); + server.register("/thatway", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + return new AbstractSimpleServerExchangeHandler() { + + @Override + protected SimpleHttpResponse handle( + final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException { + final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_PERMANENTLY); + response.addHeader(new BasicHeader("Location", target.getSchemeName() + "://test:test@" + target.toHostString() + "/")); + return response; + } + }; + } + + }); + + final HttpClientContext context = HttpClientContext.create(); + final Future future = httpclient.execute( + SimpleHttpRequest.get(target.getSchemeName() + "://test:test@" + target.toHostString() + "/thatway"), context, null); + final SimpleHttpResponse response = future.get(); + + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + Assert.assertEquals("success", response.getBody()); + } + + @Test + public void testReauthentication() throws Exception { + final AtomicLong count = new AtomicLong(0); + + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + + return new AbstractSimpleServerExchangeHandler() { + + @Override + protected SimpleHttpResponse handle( + final SimpleHttpRequest request, + final HttpCoreContext context) throws HttpException { + final String creds = (String) context.getAttribute("creds"); + if (creds == null || !creds.equals("test:test")) { + return new SimpleHttpResponse(HttpStatus.SC_UNAUTHORIZED); + } else { + // Make client re-authenticate on each fourth request + if (count.incrementAndGet() % 4 == 0) { + return new SimpleHttpResponse(HttpStatus.SC_UNAUTHORIZED); + } else { + return new SimpleHttpResponse(HttpStatus.SC_OK, "success", ContentType.TEXT_PLAIN); + } + } + } + + }; + } + + }); + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider( + new UsernamePasswordCredentials("test", "test".toCharArray())); + + final Registry authSchemeRegistry = RegistryBuilder.create() + .register("MyBasic", new AuthSchemeProvider() { + + @Override + public AuthScheme create(final HttpContext context) { + return new BasicScheme() { + + @Override + public String getName() { + return "MyBasic"; + } + + }; + } + + }) + .build(); + this.clientBuilder.setDefaultAuthSchemeRegistry(authSchemeRegistry); + + final HttpHost target = start(HttpProcessors.customServer(null) + .add(new RequestBasicAuth()) + .add(new HttpResponseInterceptor() { + + @Override + public void process( + final HttpResponse response, + final EntityDetails entityDetails, + final HttpContext context) throws HttpException, IOException { + if (response.getCode() == HttpStatus.SC_UNAUTHORIZED) { + response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "MyBasic realm=\"test realm\""); + } + } + + }) + .build(), + H1Config.DEFAULT); + + final RequestConfig config = RequestConfig.custom() + .setTargetPreferredAuthSchemes(Arrays.asList("MyBasic")) + .build(); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + for (int i = 0; i < 10; i++) { + final HttpGet httpget = new HttpGet("/"); + httpget.setConfig(config); + final Future future = httpclient.execute( + new SimpleRequestProducer(SimpleHttpRequest.get(target, "/"), config), + new SimpleResponseConsumer(), + context, null); + final SimpleHttpResponse response = future.get(); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + } + } + + @Test + public void testAuthenticationFallback() throws Exception { + server.register("*", new Supplier() { + + @Override + public AsyncServerExchangeHandler get() { + + return new AuthHandler(); + } + + }); + + final HttpHost target = start(HttpProcessors.customServer(null) + .add(new RequestBasicAuth()) + .add(new HttpResponseInterceptor() { + + @Override + public void process( + final HttpResponse response, + final EntityDetails entityDetails, + final HttpContext context) throws HttpException, IOException { + if (response.getCode() == HttpStatus.SC_UNAUTHORIZED) { + response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"test realm\" invalid"); + response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"test realm\""); + } + } + + }) + .build(), + H1Config.DEFAULT); + + + final TestCredentialsProvider credsProvider = new TestCredentialsProvider( + new UsernamePasswordCredentials("test", "test".toCharArray())); + final HttpClientContext context = HttpClientContext.create(); + context.setCredentialsProvider(credsProvider); + + final Future future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null); + final SimpleHttpResponse response = future.get(); + Assert.assertNotNull(response); + Assert.assertEquals(HttpStatus.SC_OK, response.getCode()); + Assert.assertEquals("success", response.getBody()); + final AuthScope authscope = credsProvider.getAuthScope(); + Assert.assertNotNull(authscope); + Assert.assertEquals("test realm", authscope.getRealm()); + } + +} \ No newline at end of file diff --git a/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientAuthentication.java b/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientAuthentication.java new file mode 100644 index 000000000..d81342d0e --- /dev/null +++ b/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientAuthentication.java @@ -0,0 +1,88 @@ +/* + * ==================================================================== + * 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 + * . + * + */ +package org.apache.hc.client5.http.examples; + +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.sync.BasicCredentialsProvider; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.io.ShutdownType; + +/** + * A simple example that uses HttpClient to execute an HTTP request against + * a target site that requires user authentication. + */ +public class AsyncClientAuthentication { + + public static void main(String[] args) throws Exception { + BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + new AuthScope("httpbin.org", 80), + new UsernamePasswordCredentials("user", "passwd".toCharArray())); + CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() + .setDefaultCredentialsProvider(credsProvider) + .build(); + httpclient.start(); + + final String requestUri = "http://httpbin.org/basic-auth/user/passwd"; + SimpleHttpRequest httpget = SimpleHttpRequest.get(requestUri); + + System.out.println("Executing request " + requestUri); + final Future future = httpclient.execute( + httpget, + new FutureCallback() { + + @Override + public void completed(final SimpleHttpResponse response) { + System.out.println(requestUri + "->" + response.getCode()); + System.out.println(response.getBody()); + } + + @Override + public void failed(final Exception ex) { + System.out.println(requestUri + "->" + ex); + } + + @Override + public void cancelled() { + System.out.println(requestUri + " cancelled"); + } + + }); + future.get(); + + System.out.println("Shutting down"); + httpclient.shutdown(ShutdownType.GRACEFUL); + + } +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/util/CredentialSupport.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/AuthSupport.java similarity index 74% rename from httpclient5/src/main/java/org/apache/hc/client5/http/auth/util/CredentialSupport.java rename to httpclient5/src/main/java/org/apache/hc/client5/http/impl/AuthSupport.java index 58907b7de..f2dae38e8 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/auth/util/CredentialSupport.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/AuthSupport.java @@ -25,19 +25,22 @@ * */ -package org.apache.hc.client5.http.auth.util; +package org.apache.hc.client5.http.impl; +import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.CredentialsStore; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.config.AuthSchemes; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; /** * @since 5.0 */ -public class CredentialSupport { +public class AuthSupport { public static void extractFromAuthority( final URIAuthority authority, @@ -65,4 +68,20 @@ public class CredentialSupport { new UsernamePasswordCredentials(userName, password)); } + public static HttpHost resolveAuthTarget(final HttpRequest request, final HttpRoute route) { + Args.notNull(request, "Request"); + Args.notNull(route, "Route"); + final URIAuthority authority = request.getAuthority(); + final String scheme = request.getScheme(); + final HttpHost target = authority != null ? new HttpHost(authority, scheme) : route.getTargetHost();; + if (target.getPort() < 0) { + return new HttpHost( + target.getHostName(), + route.getTargetHost().getPort(), + target.getSchemeName()); + } else { + return target; + } + } + } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java index 55096b4b4..fb3ba8f53 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java @@ -27,31 +27,74 @@ package org.apache.hc.client5.http.impl.async; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.StandardMethods; import org.apache.hc.client5.http.async.AsyncExecCallback; import org.apache.hc.client5.http.async.AsyncExecChain; import org.apache.hc.client5.http.async.AsyncExecChainHandler; +import org.apache.hc.client5.http.async.AsyncExecRuntime; +import org.apache.hc.client5.http.auth.AuthExchange; +import org.apache.hc.client5.http.auth.ChallengeType; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.CredentialsStore; -import org.apache.hc.client5.http.auth.util.CredentialSupport; +import org.apache.hc.client5.http.impl.AuthSupport; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.auth.HttpAuthenticator; +import org.apache.hc.client5.http.protocol.AuthenticationStrategy; import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.utils.URIUtils; +import org.apache.hc.core5.annotation.Contract; +import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; 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.HttpRequest; import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.AsyncEntityProducer; import org.apache.hc.core5.http.protocol.HttpCoreContext; import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.net.URIAuthority; +import org.apache.hc.core5.util.Args; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +/** + * Request executor in the request execution chain that is responsible + * for implementation of HTTP specification requirements. + *

+ * Further responsibilities such as communication with the opposite + * endpoint is delegated to the next executor in the request execution + * chain. + * + * @since 5.0 + */ +@Contract(threading = ThreadingBehavior.IMMUTABLE) class AsyncProtocolExec implements AsyncExecChainHandler { - private final HttpProcessor httpProcessor; + private final Logger log = LogManager.getLogger(getClass()); - AsyncProtocolExec(final HttpProcessor httpProcessor) { - this.httpProcessor = httpProcessor; + private final HttpProcessor httpProcessor; + private final AuthenticationStrategy targetAuthStrategy; + private final AuthenticationStrategy proxyAuthStrategy; + private final HttpAuthenticator authenticator; + + AsyncProtocolExec( + final HttpProcessor httpProcessor, + final AuthenticationStrategy targetAuthStrategy, + final AuthenticationStrategy proxyAuthStrategy) { + this.httpProcessor = Args.notNull(httpProcessor, "HTTP protocol processor"); + this.targetAuthStrategy = Args.notNull(targetAuthStrategy, "Target authentication strategy"); + this.proxyAuthStrategy = Args.notNull(proxyAuthStrategy, "Proxy authentication strategy"); + this.authenticator = new HttpAuthenticator(); } @Override @@ -63,32 +106,121 @@ class AsyncProtocolExec implements AsyncExecChainHandler { final AsyncExecCallback asyncExecCallback) throws HttpException, IOException { final HttpRoute route = scope.route; final HttpClientContext clientContext = scope.clientContext; + + if (route.getProxyHost() != null && !route.isTunnelled()) { + try { + URI uri = request.getUri(); + if (!uri.isAbsolute()) { + uri = URIUtils.rewriteURI(uri, route.getTargetHost(), true); + } else { + uri = URIUtils.rewriteURI(uri); + } + request.setPath(uri.toASCIIString()); + } catch (final URISyntaxException ex) { + throw new ProtocolException("Invalid request URI: " + request.getRequestUri(), ex); + } + } + final URIAuthority authority = request.getAuthority(); if (authority != null) { final CredentialsProvider credsProvider = clientContext.getCredentialsProvider(); if (credsProvider instanceof CredentialsStore) { - CredentialSupport.extractFromAuthority(authority, (CredentialsStore) credsProvider); + AuthSupport.extractFromAuthority(authority, (CredentialsStore) credsProvider); } } + final AtomicBoolean challenged = new AtomicBoolean(false); + internalExecute(challenged, request, entityProducer, scope, chain, asyncExecCallback); + } + + private void internalExecute( + final AtomicBoolean challenged, + final HttpRequest request, + final AsyncEntityProducer entityProducer, + final AsyncExecChain.Scope scope, + final AsyncExecChain chain, + final AsyncExecCallback asyncExecCallback) throws HttpException, IOException { + final HttpRoute route = scope.route; + final HttpClientContext clientContext = scope.clientContext; + final AsyncExecRuntime execRuntime = scope.execRuntime; + + final HttpHost target = route.getTargetHost(); + final HttpHost proxy = route.getProxyHost(); + + final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target); + final AuthExchange proxyAuthExchange = proxy != null ? clientContext.getAuthExchange(proxy) : new AuthExchange(); + clientContext.setAttribute(HttpClientContext.HTTP_ROUTE, route); clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, request); httpProcessor.process(request, entityProducer, clientContext); + if (!request.containsHeader(HttpHeaders.AUTHORIZATION)) { + if (log.isDebugEnabled()) { + log.debug("Target auth state: " + targetAuthExchange.getState()); + } + authenticator.addAuthResponse(target, ChallengeType.TARGET, request, targetAuthExchange, clientContext); + } + if (!request.containsHeader(HttpHeaders.PROXY_AUTHORIZATION) && !route.isTunnelled()) { + if (log.isDebugEnabled()) { + log.debug("Proxy auth state: " + proxyAuthExchange.getState()); + } + authenticator.addAuthResponse(proxy, ChallengeType.PROXY, request, proxyAuthExchange, clientContext); + } + chain.proceed(request, entityProducer, scope, new AsyncExecCallback() { @Override public AsyncDataConsumer handleResponse( final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException { + clientContext.setAttribute(HttpCoreContext.HTTP_RESPONSE, response); httpProcessor.process(response, entityDetails, clientContext); - return asyncExecCallback.handleResponse(response, entityDetails); + if (request.getMethod().equalsIgnoreCase(StandardMethods.TRACE.name())) { + // Do not perform authentication for TRACE request + return asyncExecCallback.handleResponse(response, entityDetails); + } + if (needAuthentication(targetAuthExchange, proxyAuthExchange, route, request, response, clientContext)) { + challenged.set(true); + return null; + } else { + challenged.set(false); + return asyncExecCallback.handleResponse(response, entityDetails); + } } @Override public void completed() { - asyncExecCallback.completed(); + if (execRuntime.isConnected()) { + if (proxyAuthExchange.getState() == AuthExchange.State.SUCCESS + && proxyAuthExchange.getAuthScheme() != null + && proxyAuthExchange.getAuthScheme().isConnectionBased()) { + log.debug("Resetting proxy auth state"); + proxyAuthExchange.reset(); + } + if (targetAuthExchange.getState() == AuthExchange.State.SUCCESS + && targetAuthExchange.getAuthScheme() != null + && targetAuthExchange.getAuthScheme().isConnectionBased()) { + log.debug("Resetting target auth state"); + targetAuthExchange.reset(); + } + } + + if (challenged.get()) { + // Reset request headers + final HttpRequest original = scope.originalRequest; + request.setHeaders(); + for (final Iterator

it = original.headerIterator(); it.hasNext(); ) { + request.addHeader(it.next()); + } + try { + internalExecute(challenged, request, entityProducer, scope, chain, asyncExecCallback); + } catch (final HttpException | IOException ex) { + asyncExecCallback.failed(ex); + } + } else { + asyncExecCallback.completed(); + } } @Override @@ -99,4 +231,37 @@ class AsyncProtocolExec implements AsyncExecChainHandler { }); } + private boolean needAuthentication( + final AuthExchange targetAuthExchange, + final AuthExchange proxyAuthExchange, + final HttpRoute route, + final HttpRequest request, + final HttpResponse response, + final HttpClientContext context) { + final RequestConfig config = context.getRequestConfig(); + if (config.isAuthenticationEnabled()) { + final HttpHost target = AuthSupport.resolveAuthTarget(request, route); + final boolean targetAuthRequested = authenticator.isChallenged( + target, ChallengeType.TARGET, response, targetAuthExchange, context); + + HttpHost proxy = route.getProxyHost(); + // if proxy is not set use target host instead + if (proxy == null) { + proxy = route.getTargetHost(); + } + final boolean proxyAuthRequested = authenticator.isChallenged( + proxy, ChallengeType.PROXY, response, proxyAuthExchange, context); + + if (targetAuthRequested) { + return authenticator.prepareAuthResponse(target, ChallengeType.TARGET, response, + targetAuthStrategy, targetAuthExchange, context); + } + if (proxyAuthRequested) { + return authenticator.prepareAuthResponse(proxy, ChallengeType.PROXY, response, + proxyAuthStrategy, proxyAuthExchange, context); + } + } + return false; + } + } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java index ab1a1ec3d..0f9f81f29 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java @@ -37,25 +37,46 @@ import java.util.List; import org.apache.hc.client5.http.ConnectionKeepAliveStrategy; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.SystemDefaultDnsResolver; import org.apache.hc.client5.http.async.AsyncExecChainHandler; +import org.apache.hc.client5.http.auth.AuthSchemeProvider; +import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.config.AuthSchemes; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.CookieSpecProvider; +import org.apache.hc.client5.http.cookie.CookieStore; import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy; import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; import org.apache.hc.client5.http.impl.DefaultUserTokenHandler; import org.apache.hc.client5.http.impl.IdleConnectionEvictor; import org.apache.hc.client5.http.impl.NamedElementChain; import org.apache.hc.client5.http.impl.NoopUserTokenHandler; +import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory; +import org.apache.hc.client5.http.impl.auth.CredSspSchemeFactory; +import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory; +import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory; +import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory; +import org.apache.hc.client5.http.impl.auth.SPNegoSchemeFactory; +import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.impl.protocol.DefaultAuthenticationStrategy; import org.apache.hc.client5.http.impl.protocol.DefaultRedirectStrategy; import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner; import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner; +import org.apache.hc.client5.http.impl.sync.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.sync.ChainElements; +import org.apache.hc.client5.http.impl.sync.CookieSpecRegistries; import org.apache.hc.client5.http.impl.sync.DefaultHttpRequestRetryHandler; import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; +import org.apache.hc.client5.http.protocol.AuthenticationStrategy; import org.apache.hc.client5.http.protocol.RedirectStrategy; +import org.apache.hc.client5.http.protocol.RequestAddCookies; +import org.apache.hc.client5.http.protocol.RequestAuthCache; import org.apache.hc.client5.http.protocol.RequestDefaultHeaders; import org.apache.hc.client5.http.protocol.RequestExpectContinue; +import org.apache.hc.client5.http.protocol.ResponseProcessCookies; import org.apache.hc.client5.http.protocol.UserTokenHandler; import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.sync.HttpRequestRetryHandler; @@ -72,11 +93,14 @@ import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponseInterceptor; import org.apache.hc.core5.http.config.CharCodingConfig; import org.apache.hc.core5.http.config.H1Config; +import org.apache.hc.core5.http.config.Lookup; +import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpProcessor; import org.apache.hc.core5.http.protocol.HttpProcessorBuilder; import org.apache.hc.core5.http.protocol.RequestUserAgent; import org.apache.hc.core5.http2.HttpVersionPolicy; @@ -179,6 +203,8 @@ public class HttpAsyncClientBuilder { private SchemePortResolver schemePortResolver; private ConnectionKeepAliveStrategy keepAliveStrategy; private UserTokenHandler userTokenHandler; + private AuthenticationStrategy targetAuthStrategy; + private AuthenticationStrategy proxyAuthStrategy; private LinkedList requestInterceptors; private LinkedList responseInterceptors; @@ -190,6 +216,11 @@ public class HttpAsyncClientBuilder { private ConnectionReuseStrategy reuseStrategy; + private Lookup authSchemeRegistry; + private Lookup cookieSpecRegistry; + private CookieStore cookieStore; + private CredentialsProvider credentialsProvider; + private String userAgent; private HttpHost proxy; private Collection defaultHeaders; @@ -201,6 +232,8 @@ public class HttpAsyncClientBuilder { private boolean systemProperties; private boolean automaticRetriesDisabled; private boolean redirectHandlingDisabled; + private boolean cookieManagementDisabled; + private boolean authCachingDisabled; private boolean connectionStateDisabled; private List closeables; @@ -307,6 +340,26 @@ public class HttpAsyncClientBuilder { return this; } + /** + * Assigns {@link AuthenticationStrategy} instance for target + * host authentication. + */ + public final HttpAsyncClientBuilder setTargetAuthenticationStrategy( + final AuthenticationStrategy targetAuthStrategy) { + this.targetAuthStrategy = targetAuthStrategy; + return this; + } + + /** + * Assigns {@link AuthenticationStrategy} instance for proxy + * authentication. + */ + public final HttpAsyncClientBuilder setProxyAuthenticationStrategy( + final AuthenticationStrategy proxyAuthStrategy) { + this.proxyAuthStrategy = proxyAuthStrategy; + return this; + } + /** * Adds this protocol interceptor to the head of the protocol processing list. */ @@ -462,6 +515,45 @@ public class HttpAsyncClientBuilder { return this; } + /** + * Assigns default {@link CredentialsProvider} instance which will be used + * for request execution if not explicitly set in the client execution + * context. + */ + public final HttpAsyncClientBuilder setDefaultCredentialsProvider(final CredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Assigns default {@link org.apache.hc.client5.http.auth.AuthScheme} registry which will + * be used for request execution if not explicitly set in the client execution + * context. + */ + public final HttpAsyncClientBuilder setDefaultAuthSchemeRegistry(final Lookup authSchemeRegistry) { + this.authSchemeRegistry = authSchemeRegistry; + return this; + } + + /** + * Assigns default {@link org.apache.hc.client5.http.cookie.CookieSpec} registry + * which will be used for request execution if not explicitly set in the client + * execution context. + */ + public final HttpAsyncClientBuilder setDefaultCookieSpecRegistry(final Lookup cookieSpecRegistry) { + this.cookieSpecRegistry = cookieSpecRegistry; + return this; + } + + /** + * Assigns default {@link CookieStore} instance which will be used for + * request execution if not explicitly set in the client execution context. + */ + public final HttpAsyncClientBuilder setDefaultCookieStore(final CookieStore cookieStore) { + this.cookieStore = cookieStore; + return this; + } + /** * Assigns default {@link RequestConfig} instance which will be used * for request execution if not explicitly set in the client execution @@ -505,6 +597,22 @@ public class HttpAsyncClientBuilder { return this; } + /** + * Disables state (cookie) management. + */ + public final HttpAsyncClientBuilder disableCookieManagement() { + this.cookieManagementDisabled = true; + return this; + } + + /** + * Disables authentication scheme caching. + */ + public final HttpAsyncClientBuilder disableAuthCaching() { + this.authCachingDisabled = true; + return this; + } + /** * Makes this instance of HttpClient proactively evict expired connections from the * connection pool using a background thread. @@ -596,6 +704,15 @@ public class HttpAsyncClientBuilder { new AsyncMainClientExec(keepAliveStrategyCopy, userTokenHandlerCopy), ChainElements.MAIN_TRANSPORT.name()); + AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy; + if (targetAuthStrategyCopy == null) { + targetAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE; + } + AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy; + if (proxyAuthStrategyCopy == null) { + proxyAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE; + } + String userAgentCopy = this.userAgent; if (userAgentCopy == null) { if (systemProperties) { @@ -629,6 +746,15 @@ public class HttpAsyncClientBuilder { new H2RequestConnControl(), new RequestUserAgent(userAgentCopy), new RequestExpectContinue()); + if (!cookieManagementDisabled) { + b.add(new RequestAddCookies()); + } + if (!authCachingDisabled) { + b.add(new RequestAuthCache()); + } + if (!cookieManagementDisabled) { + b.add(new ResponseProcessCookies()); + } if (requestInterceptors != null) { for (final RequestInterceptorEntry entry: requestInterceptors) { if (entry.postion == RequestInterceptorEntry.Postion.LAST) { @@ -644,8 +770,9 @@ public class HttpAsyncClientBuilder { } } + final HttpProcessor httpProcessor = b.build(); execChainDefinition.addFirst( - new AsyncProtocolExec(b.build()), + new AsyncProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy), ChainElements.PROTOCOL.name()); // Add request retry executor, if not disabled @@ -783,6 +910,36 @@ public class HttpAsyncClientBuilder { current = current.getPrevious(); } + Lookup authSchemeRegistryCopy = this.authSchemeRegistry; + if (authSchemeRegistryCopy == null) { + authSchemeRegistryCopy = RegistryBuilder.create() + .register(AuthSchemes.BASIC, new BasicSchemeFactory()) + .register(AuthSchemes.DIGEST, new DigestSchemeFactory()) + .register(AuthSchemes.CREDSSP, new CredSspSchemeFactory()) + .register(AuthSchemes.NTLM, new NTLMSchemeFactory()) + .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true)) + .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(SystemDefaultDnsResolver.INSTANCE, true, true)) + .build(); + } + Lookup cookieSpecRegistryCopy = this.cookieSpecRegistry; + if (cookieSpecRegistryCopy == null) { + cookieSpecRegistryCopy = CookieSpecRegistries.createDefault(); + } + + CookieStore cookieStoreCopy = this.cookieStore; + if (cookieStoreCopy == null) { + cookieStoreCopy = new BasicCookieStore(); + } + + CredentialsProvider credentialsProviderCopy = this.credentialsProvider; + if (credentialsProviderCopy == null) { + if (systemProperties) { + credentialsProviderCopy = new SystemDefaultCredentialsProvider(); + } else { + credentialsProviderCopy = new BasicCredentialsProvider(); + } + } + return new InternalHttpAsyncClient( ioReactor, execChain, @@ -791,6 +948,10 @@ public class HttpAsyncClientBuilder { connManagerCopy, routePlannerCopy, versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE, + cookieSpecRegistryCopy, + authSchemeRegistryCopy, + cookieStoreCopy, + credentialsProviderCopy, defaultRequestConfig, closeablesCopy); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java index e158f54eb..98e5b2e8b 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java @@ -36,8 +36,12 @@ import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.async.AsyncExecCallback; import org.apache.hc.client5.http.async.AsyncExecChain; import org.apache.hc.client5.http.async.AsyncExecRuntime; +import org.apache.hc.client5.http.auth.AuthSchemeProvider; +import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.config.Configurable; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.cookie.CookieSpecProvider; +import org.apache.hc.client5.http.cookie.CookieStore; import org.apache.hc.client5.http.impl.ExecSupport; import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; import org.apache.hc.client5.http.protocol.HttpClientContext; @@ -49,6 +53,7 @@ import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncDataConsumer; import org.apache.hc.core5.http.nio.AsyncRequestProducer; @@ -65,6 +70,10 @@ class InternalHttpAsyncClient extends AbstractHttpAsyncClientBase { private final AsyncExecChainElement execChain; private final HttpRoutePlanner routePlanner; private final HttpVersionPolicy versionPolicy; + private final Lookup cookieSpecRegistry; + private final Lookup authSchemeRegistry; + private final CookieStore cookieStore; + private final CredentialsProvider credentialsProvider; private final RequestConfig defaultConfig; private final List closeables; @@ -76,6 +85,10 @@ class InternalHttpAsyncClient extends AbstractHttpAsyncClientBase { final AsyncClientConnectionManager connmgr, final HttpRoutePlanner routePlanner, final HttpVersionPolicy versionPolicy, + final Lookup cookieSpecRegistry, + final Lookup authSchemeRegistry, + final CookieStore cookieStore, + final CredentialsProvider credentialsProvider, final RequestConfig defaultConfig, final List closeables) { super(ioReactor, pushConsumerRegistry, threadFactory); @@ -83,6 +96,10 @@ class InternalHttpAsyncClient extends AbstractHttpAsyncClientBase { this.execChain = execChain; this.routePlanner = routePlanner; this.versionPolicy = versionPolicy; + this.cookieSpecRegistry = cookieSpecRegistry; + this.authSchemeRegistry = authSchemeRegistry; + this.cookieStore = cookieStore; + this.credentialsProvider = credentialsProvider; this.defaultConfig = defaultConfig; this.closeables = closeables; } @@ -102,6 +119,18 @@ class InternalHttpAsyncClient extends AbstractHttpAsyncClientBase { } private void setupContext(final HttpClientContext context) { + if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) { + context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry); + } + if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) { + context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, cookieSpecRegistry); + } + if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) { + context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore); + } + if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) { + context.setAttribute(HttpClientContext.CREDS_PROVIDER, credentialsProvider); + } if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) { context.setAttribute(HttpClientContext.REQUEST_CONFIG, defaultConfig); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java index a1c842cf6..0d230d15f 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ProtocolExec.java @@ -38,7 +38,7 @@ import org.apache.hc.client5.http.auth.AuthExchange; import org.apache.hc.client5.http.auth.ChallengeType; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.CredentialsStore; -import org.apache.hc.client5.http.auth.util.CredentialSupport; +import org.apache.hc.client5.http.impl.AuthSupport; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.auth.HttpAuthenticator; import org.apache.hc.client5.http.protocol.AuthenticationStrategy; @@ -131,7 +131,7 @@ final class ProtocolExec implements ExecChainHandler { if (authority != null) { final CredentialsProvider credsProvider = context.getCredentialsProvider(); if (credsProvider instanceof CredentialsStore) { - CredentialSupport.extractFromAuthority(authority, (CredentialsStore) credsProvider); + AuthSupport.extractFromAuthority(authority, (CredentialsStore) credsProvider); } } @@ -222,15 +222,7 @@ final class ProtocolExec implements ExecChainHandler { final HttpClientContext context) { final RequestConfig config = context.getRequestConfig(); if (config.isAuthenticationEnabled()) { - final URIAuthority authority = request.getAuthority(); - final String scheme = request.getScheme(); - HttpHost target = authority != null ? new HttpHost(authority, scheme) : route.getTargetHost();; - if (target.getPort() < 0) { - target = new HttpHost( - target.getHostName(), - route.getTargetHost().getPort(), - target.getSchemeName()); - } + final HttpHost target = AuthSupport.resolveAuthTarget(request, route); final boolean targetAuthRequested = authenticator.isChallenged( target, ChallengeType.TARGET, response, targetAuthExchange, context);