0
# Authentication
1
2
Jetty Security provides multiple authentication mechanisms to support various security requirements, from simple Basic authentication to complex form-based flows.
3
4
## Base Authenticator Classes
5
6
### LoginAuthenticator
7
8
Abstract base class for login-based authenticators:
9
10
```java { .api }
11
public abstract class LoginAuthenticator implements Authenticator {
12
13
// User login and logout
14
public UserIdentity login(String username, Object password, Request request, Response response);
15
public void logout(Request request, Response response);
16
17
// Access to login service
18
public LoginService getLoginService();
19
20
// Session management
21
protected void updateSession(Request httpRequest, Response httpResponse);
22
23
// Nested authentication state classes
24
public static class UserAuthenticationSucceeded implements AuthenticationState.Succeeded {
25
public UserAuthenticationSucceeded(String method, UserIdentity userIdentity);
26
27
@Override
28
public UserIdentity getUserIdentity();
29
30
@Override
31
public String getAuthenticationType();
32
}
33
34
public static class UserAuthenticationSent extends UserAuthenticationSucceeded
35
implements AuthenticationState.ResponseSent {
36
public UserAuthenticationSent(String method, UserIdentity userIdentity);
37
}
38
39
public static class LoggedOutAuthentication implements AuthenticationState.Deferred {
40
@Override
41
public AuthenticationState authenticate(Request request, Response response, Callback callback);
42
}
43
}
44
```
45
46
## HTTP Basic Authentication
47
48
### BasicAuthenticator
49
50
Simple username/password authentication using HTTP Basic scheme:
51
52
```java { .api }
53
public class BasicAuthenticator extends LoginAuthenticator {
54
55
// Character encoding configuration
56
public Charset getCharset();
57
public void setCharset(Charset charset);
58
59
@Override
60
public String getAuthenticationType() {
61
return Authenticator.BASIC_AUTH;
62
}
63
64
@Override
65
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
66
throws ServerAuthException;
67
68
// Utility for creating Basic auth headers
69
public static String authorization(String user, String password);
70
}
71
```
72
73
### Basic Authentication Setup
74
75
```java { .api }
76
public class BasicAuthExample {
77
78
public void setupBasicAuthentication() {
79
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
80
81
// Configure login service
82
HashLoginService loginService = new HashLoginService("MyRealm");
83
loginService.setConfig(Resource.newResource("users.properties"));
84
security.setLoginService(loginService);
85
86
// Create and configure Basic authenticator
87
BasicAuthenticator basicAuth = new BasicAuthenticator();
88
basicAuth.setCharset(StandardCharsets.UTF_8);
89
security.setAuthenticator(basicAuth);
90
91
// Set realm name
92
security.setRealmName("MyRealm");
93
94
// Define protected areas
95
security.put("/api/*", Constraint.from("user"));
96
security.put("/admin/*", Constraint.from("admin"));
97
}
98
99
public void createBasicAuthHeader() {
100
// Create authorization header value
101
String authHeader = BasicAuthenticator.authorization("john", "password");
102
103
// Use in HTTP client
104
HttpClient client = new HttpClient();
105
Request request = client.newRequest("https://example.com/api/data");
106
request.header(HttpHeader.AUTHORIZATION, authHeader);
107
}
108
}
109
```
110
111
## Form-Based Authentication
112
113
### FormAuthenticator
114
115
Web form-based authentication with custom login pages:
116
117
```java { .api }
118
public class FormAuthenticator extends LoginAuthenticator {
119
120
// Form authentication constants
121
public static final String __FORM_LOGIN_PAGE = "org.eclipse.jetty.security.form_login_page";
122
public static final String __FORM_ERROR_PAGE = "org.eclipse.jetty.security.form_error_page";
123
public static final String __FORM_DISPATCH = "org.eclipse.jetty.security.dispatch";
124
public static final String __J_SECURITY_CHECK = "/j_security_check";
125
public static final String __J_USERNAME = "j_username";
126
public static final String __J_PASSWORD = "j_password";
127
128
// Constructors
129
public FormAuthenticator();
130
public FormAuthenticator(String login, String error, boolean dispatch);
131
132
// Configuration
133
public void setAlwaysSaveUri(boolean alwaysSave);
134
public boolean getAlwaysSaveUri();
135
136
@Override
137
public String getAuthenticationType() {
138
return Authenticator.FORM_AUTH;
139
}
140
141
// Security check methods
142
public boolean isJSecurityCheck(String uri);
143
public boolean isLoginOrErrorPage(String pathInContext);
144
145
@Override
146
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
147
throws ServerAuthException;
148
}
149
```
150
151
### Form Authentication Setup
152
153
```java { .api }
154
public class FormAuthExample {
155
156
public void setupFormAuthentication() {
157
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
158
159
// Configure login service
160
HashLoginService loginService = new HashLoginService("FormRealm");
161
loginService.setConfig(Resource.newResource("users.properties"));
162
security.setLoginService(loginService);
163
164
// Create form authenticator
165
FormAuthenticator formAuth = new FormAuthenticator(
166
"/login.html", // Login page
167
"/login-error.html", // Error page
168
false // Don't dispatch
169
);
170
formAuth.setAlwaysSaveUri(true); // Save original URI for redirect
171
security.setAuthenticator(formAuth);
172
173
// Security configuration
174
security.setRealmName("FormRealm");
175
security.setSessionRenewedOnAuthentication(true);
176
177
// Constraints
178
security.put("/login.html", Constraint.ALLOWED);
179
security.put("/login-error.html", Constraint.ALLOWED);
180
security.put("/css/*", Constraint.ALLOWED);
181
security.put("/js/*", Constraint.ALLOWED);
182
security.put("/app/*", Constraint.ANY_USER);
183
}
184
}
185
```
186
187
### Custom Login Pages
188
189
```html
190
<!-- login.html -->
191
<!DOCTYPE html>
192
<html>
193
<head>
194
<title>Login</title>
195
</head>
196
<body>
197
<h2>Please Login</h2>
198
<form method="post" action="j_security_check">
199
<table>
200
<tr>
201
<td>Username:</td>
202
<td><input type="text" name="j_username"/></td>
203
</tr>
204
<tr>
205
<td>Password:</td>
206
<td><input type="password" name="j_password"/></td>
207
</tr>
208
<tr>
209
<td colspan="2"><input type="submit" value="Login"/></td>
210
</tr>
211
</table>
212
</form>
213
</body>
214
</html>
215
```
216
217
## HTTP Digest Authentication
218
219
### DigestAuthenticator
220
221
More secure than Basic auth, using challenge-response mechanism:
222
223
```java { .api }
224
public class DigestAuthenticator extends LoginAuthenticator {
225
226
@Override
227
public String getAuthenticationType() {
228
return Authenticator.DIGEST_AUTH;
229
}
230
231
// Nonce configuration for security
232
public int getMaxNonceAge();
233
public void setMaxNonceAge(int maxNonceAge);
234
235
public long getNonceSecret();
236
public void setNonceSecret(long nonceSecret);
237
238
@Override
239
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
240
throws ServerAuthException;
241
}
242
```
243
244
### Digest Authentication Setup
245
246
```java { .api }
247
public class DigestAuthExample {
248
249
public void setupDigestAuthentication() {
250
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
251
252
// Configure login service with digest-compatible credentials
253
HashLoginService loginService = new HashLoginService("DigestRealm");
254
loginService.setConfig(Resource.newResource("digest-users.properties"));
255
security.setLoginService(loginService);
256
257
// Create and configure Digest authenticator
258
DigestAuthenticator digestAuth = new DigestAuthenticator();
259
digestAuth.setMaxNonceAge(60000); // 1 minute nonce lifetime
260
digestAuth.setNonceSecret(System.currentTimeMillis());
261
security.setAuthenticator(digestAuth);
262
263
security.setRealmName("DigestRealm");
264
265
// Protected areas
266
security.put("/secure/*", Constraint.ANY_USER);
267
}
268
}
269
```
270
271
## SSL Client Certificate Authentication
272
273
### SslClientCertAuthenticator
274
275
Authentication using SSL client certificates:
276
277
```java { .api }
278
public class SslClientCertAuthenticator extends LoginAuthenticator {
279
280
@Override
281
public String getAuthenticationType() {
282
return Authenticator.CERT_AUTH;
283
}
284
285
// Certificate validation configuration
286
public boolean isValidateCerts();
287
public void setValidateCerts(boolean validateCerts);
288
289
@Override
290
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
291
throws ServerAuthException;
292
}
293
```
294
295
### Client Certificate Setup
296
297
```java { .api }
298
public class ClientCertExample {
299
300
public void setupClientCertAuth() {
301
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
302
303
// Special login service for certificate authentication
304
CertificateLoginService loginService = new CertificateLoginService();
305
loginService.setName("CertRealm");
306
security.setLoginService(loginService);
307
308
// Certificate authenticator
309
SslClientCertAuthenticator certAuth = new SslClientCertAuthenticator();
310
certAuth.setValidateCerts(true); // Validate certificate chain
311
security.setAuthenticator(certAuth);
312
313
security.setRealmName("CertRealm");
314
315
// All access requires valid certificate
316
security.put("/*", Constraint.ANY_USER);
317
318
// Admin areas require specific certificate roles
319
security.put("/admin/*", Constraint.from("admin-cert"));
320
}
321
322
// Custom login service for certificates
323
public static class CertificateLoginService extends AbstractLoginService {
324
325
@Override
326
protected UserPrincipal loadUserInfo(String username) {
327
// Load user based on certificate subject
328
return findUserByCertificateSubject(username);
329
}
330
331
@Override
332
protected List<RolePrincipal> loadRoleInfo(UserPrincipal user) {
333
// Load roles based on certificate attributes
334
return getCertificateRoles(user);
335
}
336
337
private UserPrincipal findUserByCertificateSubject(String subject) {
338
// Implementation to map certificate subject to user
339
return null;
340
}
341
342
private List<RolePrincipal> getCertificateRoles(UserPrincipal user) {
343
// Implementation to determine roles from certificate
344
return new ArrayList<>();
345
}
346
}
347
}
348
```
349
350
## SPNEGO Authentication
351
352
### SPNEGOAuthenticator
353
354
Kerberos-based authentication for enterprise environments:
355
356
```java { .api }
357
public class SPNEGOAuthenticator extends LoginAuthenticator {
358
359
@Override
360
public String getAuthenticationType() {
361
return Authenticator.SPNEGO_AUTH;
362
}
363
364
@Override
365
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
366
throws ServerAuthException;
367
}
368
```
369
370
### SPNEGOUserPrincipal
371
372
Principal class for SPNEGO-authenticated users that carries encoded authentication tokens:
373
374
```java { .api }
375
public class SPNEGOUserPrincipal implements Principal {
376
377
// Constructor
378
public SPNEGOUserPrincipal(String name, String encodedToken);
379
380
// Principal interface
381
@Override
382
public String getName();
383
384
// SPNEGO-specific access
385
public String getEncodedToken();
386
}
387
```
388
389
### SPNEGO Setup
390
391
```java { .api }
392
public class SPNEGOExample {
393
394
public void setupSPNEGOAuthentication() {
395
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
396
397
// SPNEGO login service
398
SPNEGOLoginService spnegoService = new SPNEGOLoginService();
399
spnegoService.setName("SPNEGO");
400
// Configure Kerberos settings via system properties or JAAS config
401
security.setLoginService(spnegoService);
402
403
// SPNEGO authenticator
404
SPNEGOAuthenticator spnegoAuth = new SPNEGOAuthenticator();
405
security.setAuthenticator(spnegoAuth);
406
407
security.setRealmName("KERBEROS");
408
409
// Enterprise application areas
410
security.put("/app/*", Constraint.ANY_USER);
411
security.put("/admin/*", Constraint.from("Domain Admins"));
412
}
413
}
414
```
415
416
## Session Authentication
417
418
### SessionAuthentication
419
420
Manages authentication state in HTTP sessions:
421
422
```java { .api }
423
public class SessionAuthentication implements AuthenticationState.Succeeded, Session.ValueListener {
424
425
// Session attribute for authenticated user
426
public static final String AUTHENTICATED_ATTRIBUTE = "org.eclipse.jetty.security.UserIdentity";
427
428
// Constructor
429
public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials);
430
431
@Override
432
public UserIdentity getUserIdentity();
433
434
@Override
435
public String getAuthenticationType();
436
437
// Session lifecycle methods
438
@Override
439
public void valueSet(Session session, String name, Object newValue, Object oldValue);
440
441
@Override
442
public void valueRemoved(Session session, String name, Object oldValue);
443
}
444
```
445
446
## Custom Authenticator Implementation
447
448
### Creating Custom Authenticators
449
450
```java { .api }
451
public class TokenAuthenticator extends LoginAuthenticator {
452
private final String tokenHeader;
453
private final TokenValidator tokenValidator;
454
455
public TokenAuthenticator(String tokenHeader, TokenValidator validator) {
456
this.tokenHeader = tokenHeader;
457
this.tokenValidator = validator;
458
}
459
460
@Override
461
public String getAuthenticationType() {
462
return "TOKEN";
463
}
464
465
@Override
466
public AuthenticationState validateRequest(Request request, Response response, Callback callback)
467
throws ServerAuthException {
468
469
// Extract token from header
470
String token = request.getHeaders().get(tokenHeader);
471
if (token == null || token.isEmpty()) {
472
return sendChallenge(response, callback);
473
}
474
475
try {
476
// Validate token
477
TokenInfo tokenInfo = tokenValidator.validate(token);
478
if (tokenInfo == null) {
479
return AuthenticationState.SEND_FAILURE;
480
}
481
482
// Create user identity
483
UserIdentity user = createUserIdentity(tokenInfo);
484
if (user == null) {
485
return AuthenticationState.SEND_FAILURE;
486
}
487
488
return new UserAuthenticationSucceeded(getAuthenticationType(), user);
489
490
} catch (Exception e) {
491
throw new ServerAuthException("Token validation failed", e);
492
}
493
}
494
495
private AuthenticationState sendChallenge(Response response, Callback callback) {
496
response.setStatus(HttpStatus.UNAUTHORIZED_401);
497
response.getHeaders().put("WWW-Authenticate", "Token realm=\"" + getConfiguration().getRealmName() + "\"");
498
callback.succeeded();
499
return AuthenticationState.CHALLENGE;
500
}
501
502
private UserIdentity createUserIdentity(TokenInfo tokenInfo) {
503
// Create user identity from token information
504
Subject subject = new Subject();
505
UserPrincipal userPrincipal = new UserPrincipal(tokenInfo.getUsername(), Credential.getCredential(""));
506
String[] roles = tokenInfo.getRoles().toArray(new String[0]);
507
508
return getConfiguration().getIdentityService().newUserIdentity(subject, userPrincipal, roles);
509
}
510
511
// Token validation interface
512
public interface TokenValidator {
513
TokenInfo validate(String token) throws Exception;
514
}
515
516
// Token information class
517
public static class TokenInfo {
518
private final String username;
519
private final List<String> roles;
520
private final long expiration;
521
522
public TokenInfo(String username, List<String> roles, long expiration) {
523
this.username = username;
524
this.roles = roles;
525
this.expiration = expiration;
526
}
527
528
public String getUsername() { return username; }
529
public List<String> getRoles() { return roles; }
530
public long getExpiration() { return expiration; }
531
public boolean isExpired() { return System.currentTimeMillis() > expiration; }
532
}
533
}
534
```
535
536
### JWT Token Authenticator
537
538
```java { .api }
539
public class JWTAuthenticator extends TokenAuthenticator {
540
541
public JWTAuthenticator(String secretKey) {
542
super("Authorization", new JWTTokenValidator(secretKey));
543
}
544
545
@Override
546
public String getAuthenticationType() {
547
return "JWT";
548
}
549
550
private static class JWTTokenValidator implements TokenValidator {
551
private final String secretKey;
552
553
public JWTTokenValidator(String secretKey) {
554
this.secretKey = secretKey;
555
}
556
557
@Override
558
public TokenInfo validate(String token) throws Exception {
559
// Remove "Bearer " prefix if present
560
if (token.startsWith("Bearer ")) {
561
token = token.substring(7);
562
}
563
564
// JWT validation logic (using a JWT library)
565
// This is a simplified example
566
JWTClaims claims = parseAndValidateJWT(token, secretKey);
567
568
String username = claims.getSubject();
569
List<String> roles = claims.getRoles();
570
long expiration = claims.getExpiration();
571
572
return new TokenInfo(username, roles, expiration);
573
}
574
575
private JWTClaims parseAndValidateJWT(String token, String secret) throws Exception {
576
// JWT parsing and validation implementation
577
// Would typically use a library like jose4j or jsonwebtoken
578
throw new UnsupportedOperationException("JWT parsing implementation required");
579
}
580
}
581
582
private static class JWTClaims {
583
public String getSubject() { return null; }
584
public List<String> getRoles() { return Collections.emptyList(); }
585
public long getExpiration() { return 0; }
586
}
587
}
588
```
589
590
## Multi-Factor Authentication
591
592
### Multi-Factor Authenticator Wrapper
593
594
```java { .api }
595
public class MultiFactorAuthenticator implements Authenticator {
596
private final List<Authenticator> authenticators;
597
private final boolean requireAll;
598
599
public MultiFactorAuthenticator(boolean requireAll, Authenticator... authenticators) {
600
this.requireAll = requireAll;
601
this.authenticators = Arrays.asList(authenticators);
602
}
603
604
@Override
605
public void setConfiguration(Configuration configuration) {
606
authenticators.forEach(auth -> auth.setConfiguration(configuration));
607
}
608
609
@Override
610
public String getAuthenticationType() {
611
return "MULTI_FACTOR";
612
}
613
614
@Override
615
public AuthenticationState validateRequest(Request request, Response response, Callback callback)
616
throws ServerAuthException {
617
618
List<AuthenticationState.Succeeded> successes = new ArrayList<>();
619
620
for (Authenticator authenticator : authenticators) {
621
AuthenticationState state = authenticator.validateRequest(request, response, callback);
622
623
if (state instanceof AuthenticationState.Succeeded) {
624
successes.add((AuthenticationState.Succeeded) state);
625
} else if (requireAll) {
626
return state; // Fail if any required factor fails
627
}
628
}
629
630
// Check if we have enough successful authentications
631
if (requireAll && successes.size() != authenticators.size()) {
632
return AuthenticationState.SEND_FAILURE;
633
} else if (!requireAll && successes.isEmpty()) {
634
return AuthenticationState.SEND_FAILURE;
635
}
636
637
// Combine successful authentications
638
UserIdentity combinedUser = combineUserIdentities(successes);
639
return new LoginAuthenticator.UserAuthenticationSucceeded(getAuthenticationType(), combinedUser);
640
}
641
642
private UserIdentity combineUserIdentities(List<AuthenticationState.Succeeded> successes) {
643
// Logic to combine multiple user identities
644
// Could merge roles, use primary identity, etc.
645
AuthenticationState.Succeeded primary = successes.get(0);
646
return primary.getUserIdentity();
647
}
648
}
649
```
650
651
## Authentication Error Handling
652
653
### Comprehensive Error Handling
654
655
```java { .api }
656
public class AuthenticationErrorHandling {
657
658
public void handleAuthenticationErrors(Authenticator authenticator,
659
Request request, Response response, Callback callback) {
660
661
try {
662
AuthenticationState state = authenticator.validateRequest(request, response, callback);
663
664
if (state instanceof AuthenticationState.Succeeded) {
665
// Authentication successful
666
AuthenticationState.Succeeded success = (AuthenticationState.Succeeded) state;
667
logSuccessfulAuthentication(success);
668
669
} else if (state == AuthenticationState.CHALLENGE) {
670
// Challenge sent to client
671
logAuthenticationChallenge(request);
672
673
} else if (state == AuthenticationState.SEND_FAILURE) {
674
// Authentication failed
675
logAuthenticationFailure(request);
676
677
} else if (state instanceof AuthenticationState.Deferred) {
678
// Deferred authentication
679
logDeferredAuthentication(request);
680
}
681
682
} catch (ServerAuthException e) {
683
handleServerAuthException(e, request, response);
684
}
685
}
686
687
private void logSuccessfulAuthentication(AuthenticationState.Succeeded success) {
688
logger.info("Authentication successful: user={}, type={}",
689
success.getUserIdentity().getUserPrincipal().getName(),
690
success.getAuthenticationType());
691
}
692
693
private void logAuthenticationChallenge(Request request) {
694
logger.debug("Authentication challenge sent to client: {}",
695
Request.getRemoteAddr(request));
696
}
697
698
private void logAuthenticationFailure(Request request) {
699
logger.warn("Authentication failed for client: {}",
700
Request.getRemoteAddr(request));
701
}
702
703
private void logDeferredAuthentication(Request request) {
704
logger.debug("Authentication deferred for request: {}",
705
request.getHttpURI().getPath());
706
}
707
708
private void handleServerAuthException(ServerAuthException e, Request request, Response response) {
709
logger.error("Server authentication exception for client: {} - {}",
710
Request.getRemoteAddr(request), e.getMessage());
711
712
if (!response.isCommitted()) {
713
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
714
try {
715
response.getWriter().write("Authentication service unavailable");
716
} catch (IOException ioE) {
717
logger.error("Error writing authentication error response", ioE);
718
}
719
}
720
}
721
}
722
```
723
724
## Performance Optimization
725
726
### Caching and Optimization
727
728
```java { .api }
729
public class AuthenticationOptimization {
730
731
// Cached authenticator for frequently accessed resources
732
public static class CachingAuthenticator implements Authenticator {
733
private final Authenticator delegate;
734
private final Cache<String, AuthenticationState> cache;
735
736
public CachingAuthenticator(Authenticator delegate, Duration cacheTTL) {
737
this.delegate = delegate;
738
this.cache = Caffeine.newBuilder()
739
.maximumSize(1000)
740
.expireAfterWrite(cacheTTL)
741
.build();
742
}
743
744
@Override
745
public void setConfiguration(Configuration configuration) {
746
delegate.setConfiguration(configuration);
747
}
748
749
@Override
750
public String getAuthenticationType() {
751
return delegate.getAuthenticationType();
752
}
753
754
@Override
755
public AuthenticationState validateRequest(Request request, Response response, Callback callback)
756
throws ServerAuthException {
757
758
String cacheKey = buildCacheKey(request);
759
if (cacheKey != null) {
760
AuthenticationState cached = cache.getIfPresent(cacheKey);
761
if (cached instanceof AuthenticationState.Succeeded) {
762
return cached;
763
}
764
}
765
766
AuthenticationState result = delegate.validateRequest(request, response, callback);
767
768
if (result instanceof AuthenticationState.Succeeded && cacheKey != null) {
769
cache.put(cacheKey, result);
770
}
771
772
return result;
773
}
774
775
private String buildCacheKey(Request request) {
776
// Build cache key from authentication credentials
777
String authHeader = request.getHeaders().get(HttpHeader.AUTHORIZATION);
778
return authHeader != null ? authHeader.hashCode() + "" : null;
779
}
780
}
781
}
782
```
783
784
Authentication in Jetty Security provides flexible, secure, and performant solutions for various authentication requirements, from simple Basic auth to complex multi-factor scenarios.