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