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