1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.testing.async;
28
29 import static org.hamcrest.MatcherAssert.assertThat;
30
31 import java.security.SecureRandom;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.Queue;
35 import java.util.concurrent.ConcurrentLinkedQueue;
36 import java.util.concurrent.ExecutionException;
37 import java.util.concurrent.Future;
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.AuthenticationStrategy;
43 import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
44 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
45 import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
46 import org.apache.hc.client5.http.auth.AuthCache;
47 import org.apache.hc.client5.http.auth.AuthSchemeFactory;
48 import org.apache.hc.client5.http.auth.AuthScope;
49 import org.apache.hc.client5.http.auth.BearerToken;
50 import org.apache.hc.client5.http.auth.CredentialsProvider;
51 import org.apache.hc.client5.http.auth.StandardAuthScheme;
52 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
53 import org.apache.hc.client5.http.config.RequestConfig;
54 import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
55 import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
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.CredentialsProviderBuilder;
59 import org.apache.hc.client5.http.protocol.HttpClientContext;
60 import org.apache.hc.client5.testing.BasicTestAuthenticator;
61 import org.apache.hc.client5.testing.auth.Authenticator;
62 import org.apache.hc.client5.testing.auth.BearerAuthenticationHandler;
63 import org.apache.hc.core5.function.Decorator;
64 import org.apache.hc.core5.http.ContentType;
65 import org.apache.hc.core5.http.HttpHeaders;
66 import org.apache.hc.core5.http.HttpHost;
67 import org.apache.hc.core5.http.HttpRequestInterceptor;
68 import org.apache.hc.core5.http.HttpResponse;
69 import org.apache.hc.core5.http.HttpResponseInterceptor;
70 import org.apache.hc.core5.http.HttpStatus;
71 import org.apache.hc.core5.http.ProtocolException;
72 import org.apache.hc.core5.http.URIScheme;
73 import org.apache.hc.core5.http.config.Lookup;
74 import org.apache.hc.core5.http.config.Registry;
75 import org.apache.hc.core5.http.config.RegistryBuilder;
76 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
77 import org.apache.hc.core5.http.support.BasicResponseBuilder;
78 import org.apache.hc.core5.net.URIAuthority;
79 import org.apache.hc.core5.testing.nio.H2TestServer;
80 import org.hamcrest.CoreMatchers;
81 import org.junit.jupiter.api.Assertions;
82 import org.junit.jupiter.api.Test;
83 import org.mockito.Mockito;
84
85 public abstract class AbstractHttpAsyncClientAuthenticationTest<T extends CloseableHttpAsyncClient> extends AbstractIntegrationTestBase {
86
87 public AbstractHttpAsyncClientAuthenticationTest(final URIScheme scheme) {
88 super(scheme);
89 }
90
91 abstract protected H2TestServer startServer(final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator) throws Exception;
92
93 protected H2TestServer startServer() throws Exception {
94 return startServer(requestHandler -> new AuthenticatingAsyncDecorator(requestHandler, new BasicTestAuthenticator("test:test", "test realm")));
95 }
96
97 interface TestClientBuilder {
98
99 TestClientBuilder setDefaultAuthSchemeRegistry(Lookup<AuthSchemeFactory> authSchemeRegistry);
100
101 TestClientBuilder setTargetAuthenticationStrategy(AuthenticationStrategy targetAuthStrategy);
102
103 TestClientBuilder addResponseInterceptor(HttpResponseInterceptor responseInterceptor);
104
105 TestClientBuilder addRequestInterceptor(HttpRequestInterceptor requestInterceptor);
106
107 }
108
109 abstract protected T startClientCustom(final Consumer<TestClientBuilder> clientCustomizer) throws Exception;
110
111 T startClient() throws Exception {
112 return startClientCustom(c -> {});
113 }
114
115 @Test
116 public void testBasicAuthenticationNoCreds() throws Exception {
117 final H2TestServer server = startServer();
118 server.register("*", AsyncEchoHandler::new);
119 final HttpHost target = targetHost();
120
121 final T client = startClient();
122
123 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
124 final HttpClientContext context = HttpClientContext.create();
125 context.setCredentialsProvider(credsProvider);
126
127 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
128 .setHttpHost(target)
129 .setPath("/")
130 .build(), context, null);
131 final HttpResponse response = future.get();
132
133 Assertions.assertNotNull(response);
134 Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
135 Mockito.verify(credsProvider).getCredentials(
136 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
137 }
138
139 @Test
140 public void testBasicAuthenticationFailure() throws Exception {
141 final H2TestServer server = startServer();
142 server.register("*", AsyncEchoHandler::new);
143 final HttpHost target = targetHost();
144
145 final T client = startClient();
146
147 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
148 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
149 .thenReturn(new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
150 final HttpClientContext context = HttpClientContext.create();
151 context.setCredentialsProvider(credsProvider);
152
153 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
154 .setHttpHost(target)
155 .setPath("/")
156 .build(), context, null);
157 final HttpResponse response = future.get();
158
159 Assertions.assertNotNull(response);
160 Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
161 Mockito.verify(credsProvider).getCredentials(
162 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
163 }
164
165 @Test
166 public void testBasicAuthenticationSuccess() throws Exception {
167 final H2TestServer server = startServer();
168 server.register("*", AsyncEchoHandler::new);
169 final HttpHost target = targetHost();
170
171 final T client = startClient();
172
173 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
174 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
175 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
176 final HttpClientContext context = HttpClientContext.create();
177 context.setCredentialsProvider(credsProvider);
178
179 final Future<SimpleHttpResponse> future = client.execute(
180 SimpleRequestBuilder.get()
181 .setHttpHost(target)
182 .setPath("/")
183 .build(), context, null);
184 final HttpResponse response = future.get();
185
186 Assertions.assertNotNull(response);
187 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
188 Mockito.verify(credsProvider).getCredentials(
189 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
190 }
191
192 @Test
193 public void testBasicAuthenticationWithEntitySuccess() throws Exception {
194 final H2TestServer server = startServer();
195 server.register("*", AsyncEchoHandler::new);
196 final HttpHost target = targetHost();
197
198 final T client = startClient();
199
200 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
201 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
202 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
203 final HttpClientContext context = HttpClientContext.create();
204 context.setCredentialsProvider(credsProvider);
205 final Future<SimpleHttpResponse> future = client.execute(
206 SimpleRequestBuilder.put()
207 .setHttpHost(target)
208 .setPath("/")
209 .setBody("Some important stuff", ContentType.TEXT_PLAIN)
210 .build(), context, null);
211 final HttpResponse response = future.get();
212
213 Assertions.assertNotNull(response);
214 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
215 Mockito.verify(credsProvider).getCredentials(
216 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
217 }
218
219 @Test
220 public void testBasicAuthenticationExpectationFailure() throws Exception {
221 final H2TestServer server = startServer();
222 server.register("*", AsyncEchoHandler::new);
223 final HttpHost target = targetHost();
224
225 final T client = startClient();
226
227 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
228 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
229 .thenReturn(new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
230 final HttpClientContext context = HttpClientContext.create();
231 context.setCredentialsProvider(credsProvider);
232 context.setRequestConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
233 final Future<SimpleHttpResponse> future = client.execute(
234 SimpleRequestBuilder.put()
235 .setHttpHost(target)
236 .setPath("/")
237 .setBody("Some important stuff", ContentType.TEXT_PLAIN)
238 .build(), context, null);
239 final HttpResponse response = future.get();
240
241 Assertions.assertNotNull(response);
242 Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
243 }
244
245 @Test
246 public void testBasicAuthenticationExpectationSuccess() throws Exception {
247 final H2TestServer server = startServer();
248 server.register("*", AsyncEchoHandler::new);
249 final HttpHost target = targetHost();
250
251 final T client = startClient();
252
253 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
254 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
255 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
256 final HttpClientContext context = HttpClientContext.create();
257 context.setCredentialsProvider(credsProvider);
258 context.setRequestConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
259 final Future<SimpleHttpResponse> future = client.execute(
260 SimpleRequestBuilder.put()
261 .setHttpHost(target)
262 .setPath("/")
263 .setBody("Some important stuff", ContentType.TEXT_PLAIN)
264 .build(), context, null);
265 final HttpResponse response = future.get();
266
267 Assertions.assertNotNull(response);
268 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
269 Mockito.verify(credsProvider).getCredentials(
270 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
271 }
272
273 @Test
274 public void testBasicAuthenticationCredentialsCaching() throws Exception {
275 final H2TestServer server = startServer();
276 server.register("*", AsyncEchoHandler::new);
277 final HttpHost target = targetHost();
278
279 final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
280 final T client = startClientCustom(builder -> builder.setTargetAuthenticationStrategy(authStrategy));
281
282 final HttpClientContext context = HttpClientContext.create();
283 context.setCredentialsProvider(CredentialsProviderBuilder.create()
284 .add(target, "test", "test".toCharArray())
285 .build());
286
287 for (int i = 0; i < 5; i++) {
288 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
289 .setHttpHost(target)
290 .setPath("/")
291 .build(), context, null);
292 final HttpResponse response = future.get();
293 Assertions.assertNotNull(response);
294 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
295 }
296
297 Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
298 }
299
300 @Test
301 public void testBasicAuthenticationCredentialsCachingByPathPrefix() throws Exception {
302 final H2TestServer server = startServer();
303 server.register("*", AsyncEchoHandler::new);
304 final HttpHost target = targetHost();
305
306 final DefaultAuthenticationStrategy authStrategy = Mockito.spy(new DefaultAuthenticationStrategy());
307 final Queue<HttpResponse> responseQueue = new ConcurrentLinkedQueue<>();
308
309 final T client = startClientCustom(builder -> builder
310 .setTargetAuthenticationStrategy(authStrategy)
311 .addResponseInterceptor((response, entity, context)
312 -> responseQueue.add(BasicResponseBuilder.copy(response).build())));
313
314 final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()
315 .add(target, "test", "test".toCharArray())
316 .build();
317
318 final AuthCache authCache = new BasicAuthCache();
319
320 for (final String requestPath: new String[] {"/blah/a", "/blah/b?huh", "/blah/c", "/bl%61h/%61"}) {
321 final HttpClientContext context = HttpClientContext.create();
322 context.setAuthCache(authCache);
323 context.setCredentialsProvider(credentialsProvider);
324 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
325 .setHttpHost(target)
326 .setPath(requestPath)
327 .build(), context, null);
328 final HttpResponse response = future.get();
329 Assertions.assertNotNull(response);
330 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
331 }
332
333
334 Mockito.verify(authStrategy).select(Mockito.any(), Mockito.any(), Mockito.any());
335
336 assertThat(
337 responseQueue.stream().map(HttpResponse::getCode).collect(Collectors.toList()),
338 CoreMatchers.equalTo(Arrays.asList(401, 200, 200, 200, 200)));
339
340 responseQueue.clear();
341 authCache.clear();
342 Mockito.reset(authStrategy);
343
344 for (final String requestPath: new String[] {"/blah/a", "/yada/a", "/blah/blah/"}) {
345 final HttpClientContext context = HttpClientContext.create();
346 context.setCredentialsProvider(credentialsProvider);
347 context.setAuthCache(authCache);
348 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
349 .setHttpHost(target)
350 .setPath(requestPath)
351 .build(), context, null);
352 final HttpResponse response = future.get();
353 Assertions.assertNotNull(response);
354 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
355 }
356
357
358 Mockito.verify(authStrategy, Mockito.times(3)).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, 401, 200, 401, 200)));
363 }
364
365 @Test
366 public void testAuthenticationUserinfoInRequestFailure() throws Exception {
367 final H2TestServer server = startServer();
368 server.register("*", AsyncEchoHandler::new);
369 final HttpHost target = targetHost();
370
371 final T client = startClient();
372
373 final HttpClientContext context = HttpClientContext.create();
374 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
375 .setScheme(target.getSchemeName())
376 .setAuthority(new URIAuthority("test:test", target.getHostName(), target.getPort()))
377 .setPath("/")
378 .build(), context, null);
379 final ExecutionException exception = Assertions.assertThrows(ExecutionException.class, () -> future.get());
380 assertThat(exception.getCause(), CoreMatchers.instanceOf(ProtocolException.class));
381 }
382
383 @Test
384 public void testReauthentication() throws Exception {
385 final Authenticator authenticator = new BasicTestAuthenticator("test:test", "test realm") {
386
387 private final AtomicLong count = new AtomicLong(0);
388
389 @Override
390 public boolean authenticate(final URIAuthority authority, final String requestUri, final String credentials) {
391 final boolean authenticated = super.authenticate(authority, requestUri, credentials);
392 if (authenticated) {
393 return this.count.incrementAndGet() % 4 != 0;
394 }
395 return false;
396 }
397 };
398
399 final H2TestServer server = startServer(exchangeHandler -> new AuthenticatingAsyncDecorator(exchangeHandler, authenticator) {
400
401 @Override
402 protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
403 unauthorized.removeHeaders(HttpHeaders.WWW_AUTHENTICATE);
404 unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, "MyBasic realm=\"test realm\"");
405 }
406
407 });
408 server.register("*", AsyncEchoHandler::new);
409 final HttpHost target = targetHost();
410
411 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
412 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
413 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
414
415 final Registry<AuthSchemeFactory> authSchemeRegistry = RegistryBuilder.<AuthSchemeFactory>create()
416 .register("MyBasic", context -> new BasicScheme() {
417
418 private static final long serialVersionUID = 1L;
419
420 @Override
421 public String getName() {
422 return "MyBasic";
423 }
424
425 })
426 .build();
427
428 final T client = startClientCustom(builder -> builder.setDefaultAuthSchemeRegistry(authSchemeRegistry));
429
430 final RequestConfig config = RequestConfig.custom()
431 .setTargetPreferredAuthSchemes(Collections.singletonList("MyBasic"))
432 .build();
433 final HttpClientContext context = HttpClientContext.create();
434 context.setCredentialsProvider(credsProvider);
435
436 for (int i = 0; i < 10; i++) {
437 final SimpleHttpRequest request = SimpleRequestBuilder.get()
438 .setHttpHost(target)
439 .setPath("/")
440 .build();
441 request.setConfig(config);
442 final Future<SimpleHttpResponse> future = client.execute(request, context, null);
443 final SimpleHttpResponse response = future.get();
444 Assertions.assertNotNull(response);
445 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
446 }
447 }
448
449 @Test
450 public void testAuthenticationFallback() throws Exception {
451 final H2TestServer server = startServer(exchangeHandler -> new AuthenticatingAsyncDecorator(exchangeHandler, new BasicTestAuthenticator("test:test", "test realm")) {
452
453 @Override
454 protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
455 unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, StandardAuthScheme.DIGEST + " realm=\"test realm\" invalid");
456 }
457
458 });
459 server.register("*", AsyncEchoHandler::new);
460 final HttpHost target = targetHost();
461
462 final T client = startClient();
463
464 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
465 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
466 .thenReturn(new UsernamePasswordCredentials("test", "test".toCharArray()));
467 final HttpClientContext context = HttpClientContext.create();
468 context.setCredentialsProvider(credsProvider);
469
470 final Future<SimpleHttpResponse> future = client.execute(SimpleRequestBuilder.get()
471 .setHttpHost(target)
472 .setPath("/")
473 .build(), context, null);
474 final SimpleHttpResponse response = future.get();
475 Assertions.assertNotNull(response);
476 Assertions.assertEquals(HttpStatus.SC_OK, response.getCode());
477 Mockito.verify(credsProvider).getCredentials(
478 Mockito.eq(new AuthScope(target, "test realm", "basic")), Mockito.any());
479 }
480
481 private final static String CHARS = "0123456789abcdef";
482
483 @Test
484 public void testBearerTokenAuthentication() throws Exception {
485 final SecureRandom secureRandom = SecureRandom.getInstanceStrong();
486 secureRandom.setSeed(System.currentTimeMillis());
487 final StringBuilder buf = new StringBuilder();
488 for (int i = 0; i < 16; i++) {
489 buf.append(CHARS.charAt(secureRandom.nextInt(CHARS.length() - 1)));
490 }
491 final String token = buf.toString();
492 final H2TestServer server = startServer(requestHandler ->
493 new AuthenticatingAsyncDecorator(
494 requestHandler,
495 new BearerAuthenticationHandler(),
496 new BasicTestAuthenticator(token, "test realm")));
497 server.register("*", AsyncEchoHandler::new);
498 final HttpHost target = targetHost();
499
500 final T client = startClient();
501
502 final CredentialsProvider credsProvider = Mockito.mock(CredentialsProvider.class);
503 final HttpClientContext context1 = HttpClientContext.create();
504 context1.setCredentialsProvider(credsProvider);
505
506 final Future<SimpleHttpResponse> future1 = client.execute(SimpleRequestBuilder.get()
507 .setHttpHost(target)
508 .setPath("/")
509 .build(), context1, null);
510 final SimpleHttpResponse response1 = future1.get();
511 Assertions.assertNotNull(response1);
512 Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response1.getCode());
513 Mockito.verify(credsProvider).getCredentials(
514 Mockito.eq(new AuthScope(target, "test realm", "bearer")), Mockito.any());
515
516 final HttpClientContext context2 = HttpClientContext.create();
517 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
518 .thenReturn(new BearerToken(token));
519 context2.setCredentialsProvider(credsProvider);
520
521 final Future<SimpleHttpResponse> future2 = client.execute(SimpleRequestBuilder.get()
522 .setHttpHost(target)
523 .setPath("/")
524 .build(), context2, null);
525 final SimpleHttpResponse response2 = future2.get();
526 Assertions.assertNotNull(response2);
527 Assertions.assertEquals(HttpStatus.SC_OK, response2.getCode());
528
529 final HttpClientContext context3 = HttpClientContext.create();
530 Mockito.when(credsProvider.getCredentials(Mockito.any(), Mockito.any()))
531 .thenReturn(new BearerToken(token + "-expired"));
532 context3.setCredentialsProvider(credsProvider);
533
534 final Future<SimpleHttpResponse> future3 = client.execute(SimpleRequestBuilder.get()
535 .setHttpHost(target)
536 .setPath("/")
537 .build(), context3, null);
538 final SimpleHttpResponse response3 = future3.get();
539 Assertions.assertNotNull(response3);
540 Assertions.assertEquals(HttpStatus.SC_UNAUTHORIZED, response3.getCode());
541 }
542
543 }