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