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