0
# Authentication
1
2
The authentication capability provides comprehensive support for HTTP authentication mechanisms including Basic, Digest, and SPNEGO/Kerberos authentication with credential storage, automatic challenge handling, and per-request authentication configuration.
3
4
## AuthenticationStore Interface
5
6
The central storage system for authentication credentials and results.
7
8
```java { .api }
9
public interface AuthenticationStore {
10
// Authentication management
11
void addAuthentication(Authentication authentication);
12
void removeAuthentication(Authentication authentication);
13
void clearAuthentications();
14
Collection<Authentication> getAuthentications();
15
16
// Authentication result management
17
void addAuthenticationResult(Authentication.Result result);
18
void removeAuthenticationResult(Authentication.Result result);
19
void clearAuthenticationResults();
20
Authentication.Result findAuthenticationResult(URI uri);
21
}
22
```
23
24
## Authentication Interface
25
26
The base interface for all authentication mechanisms.
27
28
```java { .api }
29
public interface Authentication {
30
// Authentication matching
31
boolean matches(String type, URI uri, String realm);
32
33
// Authentication execution
34
Authentication.Result authenticate(Request request, ContentResponse response,
35
Authentication.HeaderInfo headerInfo, Attributes context);
36
37
// Header information class
38
static class HeaderInfo {
39
String getType();
40
String getRealm();
41
String getBase64();
42
Map<String, String> getParameters();
43
String getParameter(String paramName);
44
}
45
}
46
```
47
48
## Authentication.Result Interface
49
50
Represents the result of an authentication attempt.
51
52
```java { .api }
53
public interface Authentication.Result {
54
URI getURI();
55
56
void apply(Request request);
57
}
58
```
59
60
## Basic Authentication
61
62
HTTP Basic authentication using username and password credentials.
63
64
### BasicAuthentication Class
65
66
```java { .api }
67
public class BasicAuthentication implements Authentication {
68
public BasicAuthentication(URI uri, String realm, String user, String password);
69
public BasicAuthentication(String user, String password);
70
71
public String getUser();
72
public String getPassword();
73
}
74
```
75
76
### Usage Examples
77
78
```java
79
// Basic authentication for specific URI and realm
80
URI apiUri = URI.create("https://api.example.com");
81
BasicAuthentication auth = new BasicAuthentication(apiUri, "API Realm", "username", "password");
82
83
// Add to authentication store
84
client.getAuthenticationStore().addAuthentication(auth);
85
86
// Make authenticated request
87
ContentResponse response = client.GET("https://api.example.com/protected");
88
89
// Global basic authentication (matches any realm)
90
BasicAuthentication globalAuth = new BasicAuthentication("admin", "secret123");
91
client.getAuthenticationStore().addAuthentication(globalAuth);
92
```
93
94
### Preemptive Basic Authentication
95
96
```java
97
// Add authentication result directly to avoid initial challenge
98
URI uri = URI.create("https://api.example.com");
99
String credentials = Base64.getEncoder().encodeToString("user:pass".getBytes());
100
AuthenticationResult result = AuthenticationResult.from(uri, null, "Authorization", "Basic " + credentials);
101
102
client.getAuthenticationStore().addAuthenticationResult(result);
103
104
// First request will include Authorization header immediately
105
ContentResponse response = client.GET("https://api.example.com/data");
106
```
107
108
## Digest Authentication
109
110
HTTP Digest authentication providing improved security over Basic authentication.
111
112
### DigestAuthentication Class
113
114
```java { .api }
115
public class DigestAuthentication implements Authentication {
116
public DigestAuthentication(URI uri, String realm, String user, String password);
117
118
public String getUser();
119
public String getPassword();
120
}
121
```
122
123
### Usage Examples
124
125
```java
126
// Digest authentication setup
127
URI secureUri = URI.create("https://secure.example.com");
128
DigestAuthentication digestAuth = new DigestAuthentication(
129
secureUri,
130
"Secure Area",
131
"username",
132
"password"
133
);
134
135
client.getAuthenticationStore().addAuthentication(digestAuth);
136
137
// Make request - digest challenge will be handled automatically
138
ContentResponse response = client.GET("https://secure.example.com/data");
139
```
140
141
### Digest Authentication Flow
142
143
```java
144
// The client automatically handles the digest authentication flow:
145
// 1. Initial request without authentication
146
// 2. Server responds with 401 and WWW-Authenticate header
147
// 3. Client calculates digest response using provided nonce
148
// 4. Client retries request with Authorization header
149
// 5. Server validates digest and responds with requested content
150
151
DigestAuthentication auth = new DigestAuthentication(
152
URI.create("https://api.example.com"),
153
"Protected",
154
"user",
155
"pass"
156
);
157
158
client.getAuthenticationStore().addAuthentication(auth);
159
160
// This request will automatically handle the digest challenge
161
ContentResponse response = client.GET("https://api.example.com/protected-resource");
162
```
163
164
## SPNEGO Authentication
165
166
SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication for Kerberos/Windows authentication.
167
168
### SPNEGOAuthentication Class
169
170
```java { .api }
171
public class SPNEGOAuthentication implements Authentication {
172
public SPNEGOAuthentication(String serviceName);
173
174
public String getServiceName();
175
}
176
```
177
178
### Usage Examples
179
180
```java
181
// SPNEGO authentication for Kerberos
182
SPNEGOAuthentication spnegoAuth = new SPNEGOAuthentication("HTTP/server.example.com");
183
184
client.getAuthenticationStore().addAuthentication(spnegoAuth);
185
186
// SPNEGO authentication requires proper Kerberos configuration
187
// System properties or JAAS configuration may be needed
188
System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf");
189
System.setProperty("java.security.krb5.conf", "/path/to/krb5.conf");
190
191
ContentResponse response = client.GET("https://server.example.com/protected");
192
```
193
194
### SPNEGO Configuration
195
196
```java
197
// Example JAAS configuration (jaas.conf)
198
/*
199
Client {
200
com.sun.security.auth.module.Krb5LoginModule required
201
useTicketCache=true
202
renewTGT=true
203
doNotPrompt=true;
204
};
205
*/
206
207
// Example Kerberos configuration (krb5.conf)
208
/*
209
[libdefaults]
210
default_realm = EXAMPLE.COM
211
212
[realms]
213
EXAMPLE.COM = {
214
kdc = kdc.example.com
215
admin_server = admin.example.com
216
}
217
*/
218
219
public class KerberosClient {
220
public void configureSpnego() {
221
// Set system properties for Kerberos
222
System.setProperty("java.security.auth.login.config", "jaas.conf");
223
System.setProperty("java.security.krb5.conf", "krb5.conf");
224
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
225
226
// Create SPNEGO authentication
227
SPNEGOAuthentication auth = new SPNEGOAuthentication("HTTP/api.example.com");
228
client.getAuthenticationStore().addAuthentication(auth);
229
}
230
}
231
```
232
233
## Authentication Store Management
234
235
### Adding Multiple Authentication Mechanisms
236
237
```java
238
AuthenticationStore authStore = client.getAuthenticationStore();
239
240
// Add multiple authentication mechanisms
241
authStore.addAuthentication(new BasicAuthentication("user1", "pass1"));
242
authStore.addAuthentication(new DigestAuthentication(
243
URI.create("https://secure.example.com"),
244
"Secure",
245
"user2",
246
"pass2"
247
));
248
authStore.addAuthentication(new SPNEGOAuthentication("HTTP/kerberos.example.com"));
249
250
// The client will automatically select the appropriate authentication
251
// based on server challenges
252
```
253
254
### Authentication Result Management
255
256
```java
257
AuthenticationStore authStore = client.getAuthenticationStore();
258
259
// Find existing authentication result
260
URI uri = URI.create("https://api.example.com");
261
AuthenticationResult existingResult = authStore.findAuthenticationResult(uri);
262
263
if (existingResult != null) {
264
System.out.println("Found cached authentication for: " + uri);
265
} else {
266
// Add preemptive authentication
267
AuthenticationResult result = AuthenticationResult.from(
268
uri,
269
"API",
270
"Authorization",
271
"Bearer " + accessToken
272
);
273
authStore.addAuthenticationResult(result);
274
}
275
276
// Clear authentication results when tokens expire
277
authStore.clearAuthenticationResults();
278
```
279
280
## Custom Authentication
281
282
Implement custom authentication mechanisms by extending the Authentication interface.
283
284
### Custom Token Authentication
285
286
```java
287
public class BearerTokenAuthentication implements Authentication {
288
private final URI uri;
289
private final String token;
290
291
public BearerTokenAuthentication(URI uri, String token) {
292
this.uri = uri;
293
this.token = token;
294
}
295
296
@Override
297
public String getType() {
298
return "Bearer";
299
}
300
301
@Override
302
public URI getURI() {
303
return uri;
304
}
305
306
@Override
307
public String getRealm() {
308
return null; // No realm for bearer tokens
309
}
310
311
@Override
312
public boolean matches(String type, URI uri, String realm) {
313
return "Bearer".equalsIgnoreCase(type) &&
314
(this.uri == null || this.uri.equals(uri));
315
}
316
317
@Override
318
public AuthenticationResult authenticate(Request request, ContentResponse response,
319
HeaderInfo headerInfo, Context context) {
320
return AuthenticationResult.from(
321
context.getURI(),
322
context.getRealm(),
323
"Authorization",
324
"Bearer " + token
325
);
326
}
327
}
328
329
// Usage
330
BearerTokenAuthentication tokenAuth = new BearerTokenAuthentication(
331
URI.create("https://api.example.com"),
332
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
333
);
334
335
client.getAuthenticationStore().addAuthentication(tokenAuth);
336
```
337
338
### API Key Authentication
339
340
```java
341
public class ApiKeyAuthentication implements Authentication {
342
private final URI uri;
343
private final String apiKey;
344
private final String headerName;
345
346
public ApiKeyAuthentication(URI uri, String headerName, String apiKey) {
347
this.uri = uri;
348
this.headerName = headerName;
349
this.apiKey = apiKey;
350
}
351
352
@Override
353
public String getType() {
354
return "ApiKey";
355
}
356
357
@Override
358
public URI getURI() {
359
return uri;
360
}
361
362
@Override
363
public String getRealm() {
364
return null;
365
}
366
367
@Override
368
public boolean matches(String type, URI uri, String realm) {
369
return this.uri.getHost().equals(uri.getHost());
370
}
371
372
@Override
373
public AuthenticationResult authenticate(Request request, ContentResponse response,
374
HeaderInfo headerInfo, Context context) {
375
return AuthenticationResult.from(
376
context.getURI(),
377
context.getRealm(),
378
headerName,
379
apiKey
380
);
381
}
382
}
383
384
// Usage
385
ApiKeyAuthentication apiKeyAuth = new ApiKeyAuthentication(
386
URI.create("https://api.example.com"),
387
"X-API-Key",
388
"your-api-key-here"
389
);
390
391
client.getAuthenticationStore().addAuthentication(apiKeyAuth);
392
```
393
394
## Per-Request Authentication
395
396
Override authentication for specific requests without modifying the global authentication store.
397
398
### Request-Specific Headers
399
400
```java
401
// Override authentication for a single request
402
ContentResponse response = client.newRequest("https://api.example.com/data")
403
.header("Authorization", "Bearer " + specificToken)
404
.send();
405
406
// Use different API key for specific request
407
ContentResponse response2 = client.newRequest("https://different-api.com/data")
408
.header("X-API-Key", "different-api-key")
409
.send();
410
```
411
412
### Temporary Authentication
413
414
```java
415
public class TemporaryAuth {
416
private final HttpClient client;
417
private final AuthenticationStore originalStore;
418
419
public TemporaryAuth(HttpClient client) {
420
this.client = client;
421
this.originalStore = client.getAuthenticationStore();
422
}
423
424
public ContentResponse requestWithAuth(String url, Authentication auth) throws Exception {
425
// Create temporary authentication store
426
AuthenticationStore tempStore = new HttpAuthenticationStore();
427
tempStore.addAuthentication(auth);
428
429
// Temporarily replace authentication store
430
client.setAuthenticationStore(tempStore);
431
432
try {
433
return client.GET(url);
434
} finally {
435
// Restore original authentication store
436
client.setAuthenticationStore(originalStore);
437
}
438
}
439
}
440
```
441
442
## Authentication Error Handling
443
444
### Handling Authentication Failures
445
446
```java
447
client.newRequest("https://api.example.com/protected")
448
.send(result -> {
449
if (result.isSucceeded()) {
450
Response response = result.getResponse();
451
if (response.getStatus() == 401) {
452
System.err.println("Authentication failed");
453
454
// Check WWW-Authenticate header for challenge details
455
String wwwAuth = response.getHeaders().get("WWW-Authenticate");
456
System.err.println("Challenge: " + wwwAuth);
457
458
// Handle specific authentication errors
459
if (wwwAuth != null && wwwAuth.contains("expired_token")) {
460
refreshToken();
461
}
462
}
463
} else {
464
Throwable failure = result.getFailure();
465
if (failure instanceof HttpResponseException) {
466
HttpResponseException httpEx = (HttpResponseException) failure;
467
if (httpEx.getResponse().getStatus() == 401) {
468
System.err.println("Authentication challenge failed");
469
}
470
}
471
}
472
});
473
```
474
475
### Authentication Retry Logic
476
477
```java
478
public class AuthenticatedClient {
479
private final HttpClient client;
480
private String accessToken;
481
482
public ContentResponse authenticatedRequest(String url) throws Exception {
483
// First attempt with current token
484
ContentResponse response = client.newRequest(url)
485
.header("Authorization", "Bearer " + accessToken)
486
.send();
487
488
if (response.getStatus() == 401) {
489
// Token expired, refresh and retry
490
refreshAccessToken();
491
492
response = client.newRequest(url)
493
.header("Authorization", "Bearer " + accessToken)
494
.send();
495
496
if (response.getStatus() == 401) {
497
throw new SecurityException("Authentication failed after token refresh");
498
}
499
}
500
501
return response;
502
}
503
504
private void refreshAccessToken() {
505
// Implement token refresh logic
506
// This could involve calling a refresh token endpoint
507
// or re-authenticating with username/password
508
}
509
}
510
```
511
512
## Authentication Best Practices
513
514
### Secure Credential Storage
515
516
```java
517
// Avoid hardcoding credentials
518
public class SecureAuthConfig {
519
public static BasicAuthentication createFromEnvironment() {
520
String username = System.getenv("API_USERNAME");
521
String password = System.getenv("API_PASSWORD");
522
523
if (username == null || password == null) {
524
throw new IllegalStateException("Authentication credentials not configured");
525
}
526
527
return new BasicAuthentication(username, password);
528
}
529
530
public static BearerTokenAuthentication createFromTokenFile(Path tokenFile) throws IOException {
531
String token = Files.readString(tokenFile).trim();
532
return new BearerTokenAuthentication(null, token);
533
}
534
}
535
```
536
537
### Authentication Logging
538
539
```java
540
public class LoggingAuthenticationStore implements AuthenticationStore {
541
private final AuthenticationStore delegate;
542
private final Logger logger;
543
544
@Override
545
public void addAuthentication(Authentication authentication) {
546
logger.info("Adding authentication for: {} - {}",
547
authentication.getType(),
548
authentication.getURI());
549
delegate.addAuthentication(authentication);
550
}
551
552
@Override
553
public AuthenticationResult findAuthenticationResult(URI uri) {
554
AuthenticationResult result = delegate.findAuthenticationResult(uri);
555
if (result != null) {
556
logger.debug("Found cached authentication result for: {}", uri);
557
} else {
558
logger.debug("No cached authentication result for: {}", uri);
559
}
560
return result;
561
}
562
563
// Implement other methods...
564
}
565
566
// Usage
567
LoggingAuthenticationStore loggingStore = new LoggingAuthenticationStore(
568
client.getAuthenticationStore(),
569
LoggerFactory.getLogger("auth")
570
);
571
```