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