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