View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.testing.sync;
28  
29  import static org.hamcrest.MatcherAssert.assertThat;
30  
31  import java.io.ByteArrayInputStream;
32  import java.io.IOException;
33  import java.nio.charset.StandardCharsets;
34  import java.util.Arrays;
35  import java.util.Collections;
36  import java.util.Queue;
37  import java.util.concurrent.ConcurrentLinkedQueue;
38  import java.util.concurrent.atomic.AtomicLong;
39  import java.util.function.Consumer;
40  import java.util.stream.Collectors;
41  
42  import org.apache.hc.client5.http.ClientProtocolException;
43  import org.apache.hc.client5.http.auth.AuthCache;
44  import org.apache.hc.client5.http.auth.AuthScheme;
45  import org.apache.hc.client5.http.auth.AuthSchemeFactory;
46  import org.apache.hc.client5.http.auth.AuthScope;
47  import org.apache.hc.client5.http.auth.CredentialsProvider;
48  import org.apache.hc.client5.http.auth.StandardAuthScheme;
49  import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
50  import org.apache.hc.client5.http.classic.methods.HttpGet;
51  import org.apache.hc.client5.http.classic.methods.HttpPost;
52  import org.apache.hc.client5.http.classic.methods.HttpPut;
53  import org.apache.hc.client5.http.config.RequestConfig;
54  import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
55  import org.apache.hc.client5.http.impl.auth.BasicAuthCache;
56  import org.apache.hc.client5.http.impl.auth.BasicScheme;
57  import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
58  import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
59  import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
60  import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
61  import org.apache.hc.client5.http.protocol.HttpClientContext;
62  import org.apache.hc.client5.testing.BasicTestAuthenticator;
63  import org.apache.hc.client5.testing.auth.Authenticator;
64  import org.apache.hc.client5.testing.classic.AuthenticatingDecorator;
65  import org.apache.hc.client5.testing.classic.EchoHandler;
66  import org.apache.hc.client5.testing.sync.extension.TestClientResources;
67  import org.apache.hc.core5.http.ClassicHttpRequest;
68  import org.apache.hc.core5.http.ClassicHttpResponse;
69  import org.apache.hc.core5.http.HeaderElements;
70  import org.apache.hc.core5.http.HttpEntity;
71  import org.apache.hc.core5.http.HttpException;
72  import org.apache.hc.core5.http.HttpHeaders;
73  import org.apache.hc.core5.http.HttpHost;
74  import org.apache.hc.core5.http.HttpResponse;
75  import org.apache.hc.core5.http.HttpStatus;
76  import org.apache.hc.core5.http.URIScheme;
77  import org.apache.hc.core5.http.config.Http1Config;
78  import org.apache.hc.core5.http.config.Registry;
79  import org.apache.hc.core5.http.config.RegistryBuilder;
80  import org.apache.hc.core5.http.impl.HttpProcessors;
81  import org.apache.hc.core5.http.io.HttpRequestHandler;
82  import org.apache.hc.core5.http.io.entity.EntityUtils;
83  import org.apache.hc.core5.http.io.entity.InputStreamEntity;
84  import org.apache.hc.core5.http.io.entity.StringEntity;
85  import org.apache.hc.core5.http.protocol.HttpContext;
86  import org.apache.hc.core5.http.support.BasicResponseBuilder;
87  import org.apache.hc.core5.net.URIAuthority;
88  import org.apache.hc.core5.testing.classic.ClassicTestServer;
89  import org.apache.hc.core5.util.Timeout;
90  import org.hamcrest.CoreMatchers;
91  import org.junit.jupiter.api.Assertions;
92  import org.junit.jupiter.api.Test;
93  import org.junit.jupiter.api.extension.RegisterExtension;
94  import org.mockito.Mockito;
95  
96  /**
97   * Unit tests for automatic client authentication.
98   */
99  public class TestClientAuthentication {
100 
101     public static final Timeout TIMEOUT = Timeout.ofMinutes(1);
102 
103     @RegisterExtension
104     private TestClientResources testResources = new TestClientResources(URIScheme.HTTP, TIMEOUT);
105 
106     public ClassicTestServer startServer(final Authenticator authenticator) throws IOException {
107         return testResources.startServer(
108                 null,
109                 null,
110                 requestHandler -> new AuthenticatingDecorator(requestHandler, authenticator));
111     }
112 
113     public ClassicTestServer startServer() throws IOException {
114         return startServer(new BasicTestAuthenticator("test:test", "test realm"));
115     }
116 
117     public CloseableHttpClient startClient(final Consumer<HttpClientBuilder> clientCustomizer) {
118         return testResources.startClient(clientCustomizer);
119     }
120 
121     public CloseableHttpClient startClient() {
122         return testResources.startClient(builder -> {});
123     }
124 
125     public HttpHost targetHost() {
126         return testResources.targetHost();
127     }
128 
129     @Test
130     public void testBasicAuthenticationNoCreds() throws Exception {
131         final ClassicTestServer server = startServer();
132         server.registerHandler("*", new EchoHandler());
133         final HttpHost target = targetHost();
134 
135         final CloseableHttpClient client = startClient();
136 
137         final HttpClientContext context = HttpClientContext.create();
138         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
139         context.setCredentialsProvider(credsProvider);
140         final HttpGet httpget = new HttpGet("/");
141 
142         client.execute(target, httpget, context, response -> {
143             final HttpEntity entity = response.getEntity();
144             Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
145             Assertions.assertNotNull(entity);
146             EntityUtils.consume(entity);
147             return null;
148         });
149         Mockito.verify(credsProvider).getCredentials(
150                 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
151     }
152 
153     @Test
154     public void testBasicAuthenticationFailure() throws Exception {
155         final ClassicTestServer server = startServer();
156         server.registerHandler("*", new EchoHandler());
157         final HttpHost target = targetHost();
158 
159         final CloseableHttpClient client = startClient();
160 
161         final HttpClientContext context = HttpClientContext.create();
162         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
163         Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
164                 .thenReturn(new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
165         context.setCredentialsProvider(credsProvider);
166         final HttpGet httpget = new HttpGet("/");
167 
168         client.execute(target, httpget, context, response -> {
169             final HttpEntity entity = response.getEntity();
170             Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
171             Assertions.assertNotNull(entity);
172             EntityUtils.consume(entity);
173             return null;
174         });
175         Mockito.verify(credsProvider).getCredentials(
176                 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
177     }
178 
179     @Test
180     public void testBasicAuthenticationSuccess() throws Exception {
181         final ClassicTestServer server = startServer();
182         server.registerHandler("*", new EchoHandler());
183         final HttpHost target = targetHost();
184 
185         final CloseableHttpClient client = startClient();
186         final HttpGet httpget = new HttpGet("/");
187         final HttpClientContext context = HttpClientContext.create();
188         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
189         Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
190                 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
191         context.setCredentialsProvider(credsProvider);
192 
193         client.execute(target, httpget, context, response -> {
194             final HttpEntity entity = response.getEntity();
195             Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
196             Assertions.assertNotNull(entity);
197             EntityUtils.consume(entity);
198             return null;
199         });
200         Mockito.verify(credsProvider).getCredentials(
201                 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
202     }
203 
204     @Test
205     public void testBasicAuthenticationSuccessOnNonRepeatablePutExpectContinue() throws Exception {
206         final ClassicTestServer server = startServer();
207         server.registerHandler("*", new EchoHandler());
208         final HttpHost target = targetHost();
209 
210         final CloseableHttpClient client = startClient();
211 
212         final RequestConfig config = RequestConfig.custom()
213                 .setExpectContinueEnabled(true)
214                 .build();
215         final HttpPut httpput = new HttpPut("/");
216         httpput.setConfig(config);
217         httpput.setEntity(new InputStreamEntity(
218                 new ByteArrayInputStream(
219                         new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ),
220                         -1, null));
221         final HttpClientContext context = HttpClientContext.create();
222         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
223         Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
224                 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
225         context.setCredentialsProvider(credsProvider);
226 
227         client.execute(target, httpput, context, response -> {
228             final HttpEntity entity = response.getEntity();
229             Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
230             Assertions.assertNotNull(entity);
231             return null;
232         });
233     }
234 
235     @Test
236     public void testBasicAuthenticationFailureOnNonRepeatablePutDontExpectContinue() throws Exception {
237         final ClassicTestServer server = startServer();
238         server.registerHandler("*", new EchoHandler());
239         final HttpHost target = targetHost();
240 
241         final CloseableHttpClient client = startClient();
242 
243         final RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(false).build();
244         final HttpPut httpput = new HttpPut("/");
245         httpput.setConfig(config);
246         httpput.setEntity(new InputStreamEntity(
247                 new ByteArrayInputStream(
248                         new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ),
249                         -1, null));
250 
251         final HttpClientContext context = HttpClientContext.create();
252         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
253         Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
254                 .thenReturn(new UsernamePasswordCredentials("test", "boom".toCharArray()));
255         context.setCredentialsProvider(credsProvider);
256 
257         client.execute(target, httpput, context, response -> {
258             final HttpEntity entity = response.getEntity();
259             Assertions.assertEquals(401, response.getCode());
260             Assertions.assertNotNull(entity);
261             EntityUtils.consume(entity);
262             return null;
263         });
264     }
265 
266     @Test
267     public void testBasicAuthenticationSuccessOnRepeatablePost() throws Exception {
268         final ClassicTestServer server = startServer();
269         server.registerHandler("*", new EchoHandler());
270         final HttpHost target = targetHost();
271 
272         final CloseableHttpClient client = startClient();
273 
274         final HttpPost httppost = new HttpPost("/");
275         httppost.setEntity(new StringEntity("some important stuff", StandardCharsets.US_ASCII));
276 
277         final HttpClientContext context = HttpClientContext.create();
278         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
279         Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
280                 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
281         context.setCredentialsProvider(credsProvider);
282 
283         client.execute(target, httppost, context, response -> {
284             final HttpEntity entity = response.getEntity();
285             Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
286             Assertions.assertNotNull(entity);
287             EntityUtils.consume(entity);
288             return null;
289         });
290         Mockito.verify(credsProvider).getCredentials(
291                 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
292     }
293 
294     @Test
295     public void testBasicAuthenticationFailureOnNonRepeatablePost() throws Exception {
296         final ClassicTestServer server = startServer();
297         server.registerHandler("*", new EchoHandler());
298         final HttpHost target = targetHost();
299 
300         final CloseableHttpClient client = startClient();
301 
302         final HttpPost httppost = new HttpPost("/");
303         httppost.setEntity(new InputStreamEntity(
304                 new ByteArrayInputStream(
305                         new byte[] { 0,1,2,3,4,5,6,7,8,9 }), -1, null));
306 
307         final HttpClientContext context = HttpClientContext.create();
308         context.setRequestConfig(RequestConfig.custom()
309                 .setExpectContinueEnabled(false)
310                 .build());
311         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
312         Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
313                 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
314         context.setCredentialsProvider(credsProvider);
315 
316         client.execute(target, httppost, context, response -> {
317             final HttpEntity entity = response.getEntity();
318             Assertions.assertEquals(401, response.getCode());
319             Assertions.assertNotNull(entity);
320             EntityUtils.consume(entity);
321             return null;
322         });
323     }
324 
325     @Test
326     public void testBasicAuthenticationCredentialsCaching() throws Exception {
327         final ClassicTestServer server = startServer();
328         server.registerHandler("*", new EchoHandler());
329         final HttpHost target = targetHost();
330 
331         final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
332         final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
333 
334         final CloseableHttpClient client = startClient(builder -> builder
335                 .setTargetAuthenticationStrategy(authStrategy)
336                 .addResponseInterceptorLast((response, entity, context)
337                         -> responseQueue.add(BasicResponseBuilder.copy(response).build())));
338 
339         final HttpClientContext context = HttpClientContext.create();
340         context.setCredentialsProvider(CredentialsProviderBuilder.create()
341                 .add(target, "test", "test".toCharArray())
342                 .build());
343 
344         for (int i = 0; i < 5; i++) {
345             final HttpGet httpget = new HttpGet("/");
346             client.execute(target, httpget, context, response -> {
347                 final HttpEntity entity1 = response.getEntity();
348                 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
349                 Assertions.assertNotNull(entity1);
350                 EntityUtils.consume(entity1);
351                 return null;
352             });
353         }
354 
355         Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
356 
357         assertThat(
358                 responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
359                 CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200, 200)));
360     }
361 
362     @Test
363     public void testBasicAuthenticationCredentialsCachingByPathPrefix() throws Exception {
364         final ClassicTestServer server = startServer();
365         server.registerHandler("*", new EchoHandler());
366         final HttpHost target = targetHost();
367 
368         final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
369         final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
370 
371         final CloseableHttpClient client = startClient(builder -> builder
372                 .setTargetAuthenticationStrategy(authStrategy)
373                 .addResponseInterceptorLast((response, entity, context)
374                         -> responseQueue.add(BasicResponseBuilder.copy(response).build())));
375 
376         final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
377                 .add(target, "test", "test".toCharArray())
378                 .build();
379 
380         final AuthCache authCache = new BasicAuthCache();
381         final HttpClientContext context = HttpClientContext.create();
382         context.setAuthCache(authCache);
383         context.setCredentialsProvider(credentialsProvider);
384 
385         for (final String requestPath: new String[] {"/blah/a", "/blah/b?huh", "/blah/c", "/bl%61h/%61"}) {
386             final HttpGet httpget = new HttpGet(requestPath);
387             client.execute(target, httpget, context, response -> {
388                 final HttpEntity entity1 = response.getEntity();
389                 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
390                 Assertions.assertNotNull(entity1);
391                 EntityUtils.consume(entity1);
392                 return null;
393             });
394         }
395 
396         // There should be only single auth strategy call for all successful message exchanges
397         Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
398 
399         assertThat(
400                 responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
401                 CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200)));
402 
403         responseQueue.clear();
404         authCache.clear();
405         Mockito.reset(authStrategy);
406 
407         for (final String requestPath: new String[] {"/blah/a", "/yada/a", "/blah/blah/", "/buh/a"}) {
408             final HttpGet httpget = new HttpGet(requestPath);
409             client.execute(target, httpget, context, response -> {
410                 final HttpEntity entity1 = response.getEntity();
411                 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
412                 Assertions.assertNotNull(entity1);
413                 EntityUtils.consume(entity1);
414                 return null;
415             });
416         }
417 
418         // There should be an auth strategy call for all successful message exchanges
419         Mockito.verify(authStrategy, Mockito.times(2)).select(Mockito.any(), Mockito.any(), Mockito.any());
420 
421         assertThat(
422                 responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
423                 CoreMatchers.equalTo(Arrays.asList(200, 401, 200, 200, 401, 200)));
424     }
425 
426     @Test
427     public void testAuthenticationCredentialsCachingReauthenticationOnDifferentRealm() throws Exception {
428         final ClassicTestServer server = startServer(new Authenticator() {
429 
430             @Override
431             public boolean authenticate(final URIAuthority authority, final String requestUri, final String credentials) {
432                 if (requestUri.equals("/this")) {
433                     return "test:this".equals(credentials);
434                 } else if (requestUri.equals("/that")) {
435                     return "test:that".equals(credentials);
436                 } else {
437                     return "test:test".equals(credentials);
438                 }
439             }
440 
441             @Override
442             public String getRealm(final URIAuthority authority, final String requestUri) {
443                 if (requestUri.equals("/this")) {
444                     return "this realm";
445                 } else if (requestUri.equals("/that")) {
446                     return "that realm";
447                 } else {
448                     return "test realm";
449                 }
450             }
451 
452         });
453         server.registerHandler("*", new EchoHandler());
454         final HttpHost target = targetHost();
455 
456         final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
457 
458         final CloseableHttpClient client = startClient(builder -> builder
459                 .setTargetAuthenticationStrategy(authStrategy)
460         );
461 
462         final CredentialsProvider credsProvider = CredentialsProviderBuilder.create()
463                 .add(new AuthScope(target, "this realm", null), "test", "this".toCharArray())
464                 .add(new AuthScope(target, "that realm", null), "test", "that".toCharArray())
465                 .build();
466 
467         final HttpClientContext context = HttpClientContext.create();
468         context.setCredentialsProvider(credsProvider);
469 
470         final HttpGet httpget1 = new HttpGet("/this");
471 
472         client.execute(target, httpget1, context, response -> {
473             final HttpEntity entity1 = response.getEntity();
474             Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
475             Assertions.assertNotNull(entity1);
476             EntityUtils.consume(entity1);
477             return null;
478         });
479 
480         final HttpGet httpget2 = new HttpGet("/this");
481 
482         client.execute(target, httpget2, context, response -> {
483             final HttpEntity entity2 = response.getEntity();
484             Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
485             Assertions.assertNotNull(entity2);
486             EntityUtils.consume(entity2);
487             return null;
488         });
489 
490         final HttpGet httpget3 = new HttpGet("/that");
491 
492         client.execute(target, httpget3, context, response -> {
493             final HttpEntity entity3 = response.getEntity();
494             Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
495             Assertions.assertNotNull(entity3);
496             EntityUtils.consume(entity3);
497             return null;
498         });
499 
500         Mockito.verify(authStrategy, Mockito.times(2)).select(Mockito.any(), Mockito.any(), Mockito.any());
501     }
502 
503     @Test
504     public void testAuthenticationUserinfoInRequest() throws Exception {
505         final ClassicTestServer server = startServer();
506         server.registerHandler("*", new EchoHandler());
507         final HttpHost target = targetHost();
508 
509         final CloseableHttpClient client = startClient();
510         final HttpGet httpget = new HttpGet("http://test:test@" +  target.toHostString() + "/");
511 
512         final HttpClientContext context = HttpClientContext.create();
513         Assertions.assertThrows(ClientProtocolException.class, () -> client.execute(target, httpget, context, response -> null));
514     }
515 
516     @Test
517     public void testPreemptiveAuthentication() throws Exception {
518         final Authenticator authenticator = Mockito.spy(new BasicTestAuthenticator("test:test", "test realm"));
519         final ClassicTestServer server = startServer(authenticator);
520         server.registerHandler("*", new EchoHandler());
521         final HttpHost target = targetHost();
522 
523         final CloseableHttpClient client = startClient();
524 
525         final BasicScheme basicScheme = new BasicScheme();
526         basicScheme.initPreemptive(new UsernamePasswordCredentials("test", "test".toCharArray()));
527         final HttpClientContext context = HttpClientContext.create();
528         final AuthCache authCache = new BasicAuthCache();
529         authCache.put(target, basicScheme);
530         context.setAuthCache(authCache);
531 
532         final HttpGet httpget = new HttpGet("/");
533         client.execute(target, httpget, context, response -> {
534             final HttpEntity entity1 = response.getEntity();
535             Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
536             Assertions.assertNotNull(entity1);
537             EntityUtils.consume(entity1);
538             return null;
539         });
540 
541         Mockito.verify(authenticator).authenticate(Mockito.any(), Mockito.any(), Mockito.any());
542     }
543 
544     @Test
545     public void testPreemptiveAuthenticationFailure() throws Exception {
546         final Authenticator authenticator = Mockito.spy(new BasicTestAuthenticator("test:test", "test realm"));
547         final ClassicTestServer server = startServer(authenticator);
548         server.registerHandler("*", new EchoHandler());
549         final HttpHost target = targetHost();
550 
551         final CloseableHttpClient client = startClient();
552 
553         final HttpClientContext context = HttpClientContext.create();
554         final AuthCache authCache = new BasicAuthCache();
555         authCache.put(target, new BasicScheme());
556         context.setAuthCache(authCache);
557         context.setCredentialsProvider(CredentialsProviderBuilder.create()
558                 .add(target, "test", "stuff".toCharArray())
559                 .build());
560 
561         final HttpGet httpget = new HttpGet("/");
562         client.execute(target, httpget, context, response -> {
563             final HttpEntity entity1 = response.getEntity();
564             Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
565             Assertions.assertNotNull(entity1);
566             EntityUtils.consume(entity1);
567             return null;
568         });
569 
570         Mockito.verify(authenticator).authenticate(Mockito.any(), Mockito.any(), Mockito.any());
571     }
572 
573     static class ProxyAuthHandler implements HttpRequestHandler {
574 
575         @Override
576         public void handle(
577                 final ClassicHttpRequest request,
578                 final ClassicHttpResponse response,
579                 final HttpContext context) throws HttpException, IOException {
580             final String creds = (String) context.getAttribute("creds");
581             if (creds == null || !creds.equals("test:test")) {
582                 response.setCode(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED);
583             } else {
584                 response.setCode(HttpStatus.SC_OK);
585                 final StringEntity entity = new StringEntity("success", StandardCharsets.US_ASCII);
586                 response.setEntity(entity);
587             }
588         }
589 
590     }
591 
592     @Test
593     public void testAuthenticationTargetAsProxy() throws Exception {
594         final ClassicTestServer server = testResources.startServer(null, null, null);
595         server.registerHandler("*", new ProxyAuthHandler());
596         final HttpHost target = testResources.targetHost();
597 
598         final CloseableHttpClient client = testResources.startClient(builder -> {});
599 
600         final HttpClientContext context = HttpClientContext.create();
601         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
602         context.setCredentialsProvider(credsProvider);
603 
604         final HttpGet httpget = new HttpGet("/");
605         client.execute(target, httpget, context, response -> {
606             final HttpEntity entity = response.getEntity();
607             Assertions.assertEquals(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED, response.getCode());
608             EntityUtils.consume(entity);
609             return null;
610         });
611     }
612 
613     @Test
614     public void testConnectionCloseAfterAuthenticationSuccess() throws Exception {
615         final ClassicTestServer server = testResources.startServer(
616                 Http1Config.DEFAULT,
617                 HttpProcessors.server(),
618                 requestHandler -> new AuthenticatingDecorator(requestHandler, new BasicTestAuthenticator("test:test", "test realm")) {
619 
620                     @Override
621                     protected void customizeUnauthorizedResponse(final ClassicHttpResponse unauthorized) {
622                         unauthorized.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
623                     }
624 
625                 }
626         );
627         server.registerHandler("*", new EchoHandler());
628         final HttpHost target = targetHost();
629 
630         final CloseableHttpClient client = startClient();
631 
632         final HttpClientContext context = HttpClientContext.create();
633         final CredentialsProvider credsProvider = CredentialsProviderBuilder.create()
634                 .add(target, "test", "test".toCharArray())
635                 .build();
636         context.setCredentialsProvider(credsProvider);
637 
638         for (int i = 0; i < 2; i++) {
639             final HttpGet httpget = new HttpGet("/");
640 
641             client.execute(target, httpget, context, response -> {
642                 EntityUtils.consume(response.getEntity());
643                 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
644                 return null;
645             });
646         }
647     }
648 
649     @Test
650     public void testReauthentication() throws Exception {
651         final BasicSchemeFactory myBasicAuthSchemeFactory = new BasicSchemeFactory() {
652 
653             @Override
654             public AuthScheme create(final HttpContext context) {
655                 return new BasicScheme() {
656                     private static final long serialVersionUID = 1L;
657 
658                     @Override
659                     public String getName() {
660                         return "MyBasic";
661                     }
662 
663                 };
664             }
665 
666         };
667 
668         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
669         Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
670                 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
671 
672         final RequestConfig config = RequestConfig.custom()
673                 .setTargetPreferredAuthSchemes(Collections.singletonList("MyBasic"))
674                 .build();
675         final Registry<AuthSchemeFactory> authSchemeRegistry = RegistryBuilder.<AuthSchemeFactory>create()
676                 .register("MyBasic", myBasicAuthSchemeFactory)
677                 .build();
678 
679         final Authenticator authenticator = new BasicTestAuthenticator("test:test", "test realm") {
680 
681             private final AtomicLong count = new AtomicLong(0);
682 
683             @Override
684             public boolean authenticate(final URIAuthority authority, final String requestUri, final String credentials) {
685                 final boolean authenticated = super.authenticate(authority, requestUri, credentials);
686                 if (authenticated) {
687                     return this.count.incrementAndGet() % 4 != 0;
688                 }
689                 return false;
690             }
691         };
692 
693         final ClassicTestServer server = testResources.startServer(
694                 Http1Config.DEFAULT,
695                 HttpProcessors.server(),
696                 requestHandler -> new AuthenticatingDecorator(requestHandler, authenticator) {
697 
698                     @Override
699                     protected void customizeUnauthorizedResponse(final ClassicHttpResponse unauthorized) {
700                         unauthorized.removeHeaders(HttpHeaders.WWW_AUTHENTICATE);
701                         unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, "MyBasic realm=\"test realm\"");
702                     }
703 
704                 }
705         );
706         server.registerHandler("*", new EchoHandler());
707         final HttpHost target = targetHost();
708 
709         final CloseableHttpClient client = startClient(builder -> builder
710                 .setDefaultAuthSchemeRegistry(authSchemeRegistry)
711                 .setDefaultCredentialsProvider(credsProvider)
712         );
713 
714         final HttpClientContext context = HttpClientContext.create();
715         for (int i = 0; i < 10; i++) {
716             final HttpGet httpget = new HttpGet("/");
717             httpget.setConfig(config);
718             client.execute(target, httpget, context, response -> {
719                 final HttpEntity entity = response.getEntity();
720                 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
721                 Assertions.assertNotNull(entity);
722                 EntityUtils.consume(entity);
723                 return null;
724             });
725         }
726     }
727 
728     @Test
729     public void testAuthenticationFallback() throws Exception {
730         final ClassicTestServer server = testResources.startServer(
731                 Http1Config.DEFAULT,
732                 HttpProcessors.server(),
733                 requestHandler -> new AuthenticatingDecorator(requestHandler, new BasicTestAuthenticator("test:test", "test realm")) {
734 
735                     @Override
736                     protected void customizeUnauthorizedResponse(final ClassicHttpResponse unauthorized) {
737                         unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.DIGEST + " realm=\"test realm\" invalid");
738                     }
739 
740                 }
741         );
742         server.registerHandler("*", new EchoHandler());
743         final HttpHost target = targetHost();
744 
745         final CloseableHttpClient client = startClient();
746 
747         final HttpClientContext context = HttpClientContext.create();
748         final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
749         Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
750                 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
751         context.setCredentialsProvider(credsProvider);
752         final HttpGet httpget = new HttpGet("/");
753 
754         client.execute(target, httpget, context, response -> {
755             final HttpEntity entity = response.getEntity();
756             Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
757             Assertions.assertNotNull(entity);
758             EntityUtils.consume(entity);
759             return null;
760         });
761         Mockito.verify(credsProvider).getCredentials(
762                 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
763     }
764 
765 }