Security framework for Eclipse Jetty providing authentication, authorization, and user identity management.
—
Jetty Security provides multiple authentication mechanisms to support various security requirements, from simple Basic authentication to complex form-based flows.
Abstract base class for login-based authenticators:
public abstract class LoginAuthenticator implements Authenticator {
// User login and logout
public UserIdentity login(String username, Object password, Request request, Response response);
public void logout(Request request, Response response);
// Access to login service
public LoginService getLoginService();
// Session management
protected void updateSession(Request httpRequest, Response httpResponse);
// Nested authentication state classes
public static class UserAuthenticationSucceeded implements AuthenticationState.Succeeded {
public UserAuthenticationSucceeded(String method, UserIdentity userIdentity);
@Override
public UserIdentity getUserIdentity();
@Override
public String getAuthenticationType();
}
public static class UserAuthenticationSent extends UserAuthenticationSucceeded
implements AuthenticationState.ResponseSent {
public UserAuthenticationSent(String method, UserIdentity userIdentity);
}
public static class LoggedOutAuthentication implements AuthenticationState.Deferred {
@Override
public AuthenticationState authenticate(Request request, Response response, Callback callback);
}
}Simple username/password authentication using HTTP Basic scheme:
public class BasicAuthenticator extends LoginAuthenticator {
// Character encoding configuration
public Charset getCharset();
public void setCharset(Charset charset);
@Override
public String getAuthenticationType() {
return Authenticator.BASIC_AUTH;
}
@Override
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
throws ServerAuthException;
// Utility for creating Basic auth headers
public static String authorization(String user, String password);
}public class BasicAuthExample {
public void setupBasicAuthentication() {
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
// Configure login service
HashLoginService loginService = new HashLoginService("MyRealm");
loginService.setConfig(Resource.newResource("users.properties"));
security.setLoginService(loginService);
// Create and configure Basic authenticator
BasicAuthenticator basicAuth = new BasicAuthenticator();
basicAuth.setCharset(StandardCharsets.UTF_8);
security.setAuthenticator(basicAuth);
// Set realm name
security.setRealmName("MyRealm");
// Define protected areas
security.put("/api/*", Constraint.from("user"));
security.put("/admin/*", Constraint.from("admin"));
}
public void createBasicAuthHeader() {
// Create authorization header value
String authHeader = BasicAuthenticator.authorization("john", "password");
// Use in HTTP client
HttpClient client = new HttpClient();
Request request = client.newRequest("https://example.com/api/data");
request.header(HttpHeader.AUTHORIZATION, authHeader);
}
}Web form-based authentication with custom login pages:
public class FormAuthenticator extends LoginAuthenticator {
// Form authentication constants
public static final String __FORM_LOGIN_PAGE = "org.eclipse.jetty.security.form_login_page";
public static final String __FORM_ERROR_PAGE = "org.eclipse.jetty.security.form_error_page";
public static final String __FORM_DISPATCH = "org.eclipse.jetty.security.dispatch";
public static final String __J_SECURITY_CHECK = "/j_security_check";
public static final String __J_USERNAME = "j_username";
public static final String __J_PASSWORD = "j_password";
// Constructors
public FormAuthenticator();
public FormAuthenticator(String login, String error, boolean dispatch);
// Configuration
public void setAlwaysSaveUri(boolean alwaysSave);
public boolean getAlwaysSaveUri();
@Override
public String getAuthenticationType() {
return Authenticator.FORM_AUTH;
}
// Security check methods
public boolean isJSecurityCheck(String uri);
public boolean isLoginOrErrorPage(String pathInContext);
@Override
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
throws ServerAuthException;
}public class FormAuthExample {
public void setupFormAuthentication() {
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
// Configure login service
HashLoginService loginService = new HashLoginService("FormRealm");
loginService.setConfig(Resource.newResource("users.properties"));
security.setLoginService(loginService);
// Create form authenticator
FormAuthenticator formAuth = new FormAuthenticator(
"/login.html", // Login page
"/login-error.html", // Error page
false // Don't dispatch
);
formAuth.setAlwaysSaveUri(true); // Save original URI for redirect
security.setAuthenticator(formAuth);
// Security configuration
security.setRealmName("FormRealm");
security.setSessionRenewedOnAuthentication(true);
// Constraints
security.put("/login.html", Constraint.ALLOWED);
security.put("/login-error.html", Constraint.ALLOWED);
security.put("/css/*", Constraint.ALLOWED);
security.put("/js/*", Constraint.ALLOWED);
security.put("/app/*", Constraint.ANY_USER);
}
}<!-- login.html -->
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h2>Please Login</h2>
<form method="post" action="j_security_check">
<table>
<tr>
<td>Username:</td>
<td><input type="text" name="j_username"/></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="j_password"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Login"/></td>
</tr>
</table>
</form>
</body>
</html>More secure than Basic auth, using challenge-response mechanism:
public class DigestAuthenticator extends LoginAuthenticator {
@Override
public String getAuthenticationType() {
return Authenticator.DIGEST_AUTH;
}
// Nonce configuration for security
public int getMaxNonceAge();
public void setMaxNonceAge(int maxNonceAge);
public long getNonceSecret();
public void setNonceSecret(long nonceSecret);
@Override
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
throws ServerAuthException;
}public class DigestAuthExample {
public void setupDigestAuthentication() {
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
// Configure login service with digest-compatible credentials
HashLoginService loginService = new HashLoginService("DigestRealm");
loginService.setConfig(Resource.newResource("digest-users.properties"));
security.setLoginService(loginService);
// Create and configure Digest authenticator
DigestAuthenticator digestAuth = new DigestAuthenticator();
digestAuth.setMaxNonceAge(60000); // 1 minute nonce lifetime
digestAuth.setNonceSecret(System.currentTimeMillis());
security.setAuthenticator(digestAuth);
security.setRealmName("DigestRealm");
// Protected areas
security.put("/secure/*", Constraint.ANY_USER);
}
}Authentication using SSL client certificates:
public class SslClientCertAuthenticator extends LoginAuthenticator {
@Override
public String getAuthenticationType() {
return Authenticator.CERT_AUTH;
}
// Certificate validation configuration
public boolean isValidateCerts();
public void setValidateCerts(boolean validateCerts);
@Override
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
throws ServerAuthException;
}public class ClientCertExample {
public void setupClientCertAuth() {
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
// Special login service for certificate authentication
CertificateLoginService loginService = new CertificateLoginService();
loginService.setName("CertRealm");
security.setLoginService(loginService);
// Certificate authenticator
SslClientCertAuthenticator certAuth = new SslClientCertAuthenticator();
certAuth.setValidateCerts(true); // Validate certificate chain
security.setAuthenticator(certAuth);
security.setRealmName("CertRealm");
// All access requires valid certificate
security.put("/*", Constraint.ANY_USER);
// Admin areas require specific certificate roles
security.put("/admin/*", Constraint.from("admin-cert"));
}
// Custom login service for certificates
public static class CertificateLoginService extends AbstractLoginService {
@Override
protected UserPrincipal loadUserInfo(String username) {
// Load user based on certificate subject
return findUserByCertificateSubject(username);
}
@Override
protected List<RolePrincipal> loadRoleInfo(UserPrincipal user) {
// Load roles based on certificate attributes
return getCertificateRoles(user);
}
private UserPrincipal findUserByCertificateSubject(String subject) {
// Implementation to map certificate subject to user
return null;
}
private List<RolePrincipal> getCertificateRoles(UserPrincipal user) {
// Implementation to determine roles from certificate
return new ArrayList<>();
}
}
}Kerberos-based authentication for enterprise environments:
public class SPNEGOAuthenticator extends LoginAuthenticator {
@Override
public String getAuthenticationType() {
return Authenticator.SPNEGO_AUTH;
}
@Override
public AuthenticationState validateRequest(Request req, Response res, Callback callback)
throws ServerAuthException;
}Principal class for SPNEGO-authenticated users that carries encoded authentication tokens:
public class SPNEGOUserPrincipal implements Principal {
// Constructor
public SPNEGOUserPrincipal(String name, String encodedToken);
// Principal interface
@Override
public String getName();
// SPNEGO-specific access
public String getEncodedToken();
}public class SPNEGOExample {
public void setupSPNEGOAuthentication() {
SecurityHandler.PathMapped security = new SecurityHandler.PathMapped();
// SPNEGO login service
SPNEGOLoginService spnegoService = new SPNEGOLoginService();
spnegoService.setName("SPNEGO");
// Configure Kerberos settings via system properties or JAAS config
security.setLoginService(spnegoService);
// SPNEGO authenticator
SPNEGOAuthenticator spnegoAuth = new SPNEGOAuthenticator();
security.setAuthenticator(spnegoAuth);
security.setRealmName("KERBEROS");
// Enterprise application areas
security.put("/app/*", Constraint.ANY_USER);
security.put("/admin/*", Constraint.from("Domain Admins"));
}
}Manages authentication state in HTTP sessions:
public class SessionAuthentication implements AuthenticationState.Succeeded, Session.ValueListener {
// Session attribute for authenticated user
public static final String AUTHENTICATED_ATTRIBUTE = "org.eclipse.jetty.security.UserIdentity";
// Constructor
public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials);
@Override
public UserIdentity getUserIdentity();
@Override
public String getAuthenticationType();
// Session lifecycle methods
@Override
public void valueSet(Session session, String name, Object newValue, Object oldValue);
@Override
public void valueRemoved(Session session, String name, Object oldValue);
}public class TokenAuthenticator extends LoginAuthenticator {
private final String tokenHeader;
private final TokenValidator tokenValidator;
public TokenAuthenticator(String tokenHeader, TokenValidator validator) {
this.tokenHeader = tokenHeader;
this.tokenValidator = validator;
}
@Override
public String getAuthenticationType() {
return "TOKEN";
}
@Override
public AuthenticationState validateRequest(Request request, Response response, Callback callback)
throws ServerAuthException {
// Extract token from header
String token = request.getHeaders().get(tokenHeader);
if (token == null || token.isEmpty()) {
return sendChallenge(response, callback);
}
try {
// Validate token
TokenInfo tokenInfo = tokenValidator.validate(token);
if (tokenInfo == null) {
return AuthenticationState.SEND_FAILURE;
}
// Create user identity
UserIdentity user = createUserIdentity(tokenInfo);
if (user == null) {
return AuthenticationState.SEND_FAILURE;
}
return new UserAuthenticationSucceeded(getAuthenticationType(), user);
} catch (Exception e) {
throw new ServerAuthException("Token validation failed", e);
}
}
private AuthenticationState sendChallenge(Response response, Callback callback) {
response.setStatus(HttpStatus.UNAUTHORIZED_401);
response.getHeaders().put("WWW-Authenticate", "Token realm=\"" + getConfiguration().getRealmName() + "\"");
callback.succeeded();
return AuthenticationState.CHALLENGE;
}
private UserIdentity createUserIdentity(TokenInfo tokenInfo) {
// Create user identity from token information
Subject subject = new Subject();
UserPrincipal userPrincipal = new UserPrincipal(tokenInfo.getUsername(), Credential.getCredential(""));
String[] roles = tokenInfo.getRoles().toArray(new String[0]);
return getConfiguration().getIdentityService().newUserIdentity(subject, userPrincipal, roles);
}
// Token validation interface
public interface TokenValidator {
TokenInfo validate(String token) throws Exception;
}
// Token information class
public static class TokenInfo {
private final String username;
private final List<String> roles;
private final long expiration;
public TokenInfo(String username, List<String> roles, long expiration) {
this.username = username;
this.roles = roles;
this.expiration = expiration;
}
public String getUsername() { return username; }
public List<String> getRoles() { return roles; }
public long getExpiration() { return expiration; }
public boolean isExpired() { return System.currentTimeMillis() > expiration; }
}
}public class JWTAuthenticator extends TokenAuthenticator {
public JWTAuthenticator(String secretKey) {
super("Authorization", new JWTTokenValidator(secretKey));
}
@Override
public String getAuthenticationType() {
return "JWT";
}
private static class JWTTokenValidator implements TokenValidator {
private final String secretKey;
public JWTTokenValidator(String secretKey) {
this.secretKey = secretKey;
}
@Override
public TokenInfo validate(String token) throws Exception {
// Remove "Bearer " prefix if present
if (token.startsWith("Bearer ")) {
token = token.substring(7);
}
// JWT validation logic (using a JWT library)
// This is a simplified example
JWTClaims claims = parseAndValidateJWT(token, secretKey);
String username = claims.getSubject();
List<String> roles = claims.getRoles();
long expiration = claims.getExpiration();
return new TokenInfo(username, roles, expiration);
}
private JWTClaims parseAndValidateJWT(String token, String secret) throws Exception {
// JWT parsing and validation implementation
// Would typically use a library like jose4j or jsonwebtoken
throw new UnsupportedOperationException("JWT parsing implementation required");
}
}
private static class JWTClaims {
public String getSubject() { return null; }
public List<String> getRoles() { return Collections.emptyList(); }
public long getExpiration() { return 0; }
}
}public class MultiFactorAuthenticator implements Authenticator {
private final List<Authenticator> authenticators;
private final boolean requireAll;
public MultiFactorAuthenticator(boolean requireAll, Authenticator... authenticators) {
this.requireAll = requireAll;
this.authenticators = Arrays.asList(authenticators);
}
@Override
public void setConfiguration(Configuration configuration) {
authenticators.forEach(auth -> auth.setConfiguration(configuration));
}
@Override
public String getAuthenticationType() {
return "MULTI_FACTOR";
}
@Override
public AuthenticationState validateRequest(Request request, Response response, Callback callback)
throws ServerAuthException {
List<AuthenticationState.Succeeded> successes = new ArrayList<>();
for (Authenticator authenticator : authenticators) {
AuthenticationState state = authenticator.validateRequest(request, response, callback);
if (state instanceof AuthenticationState.Succeeded) {
successes.add((AuthenticationState.Succeeded) state);
} else if (requireAll) {
return state; // Fail if any required factor fails
}
}
// Check if we have enough successful authentications
if (requireAll && successes.size() != authenticators.size()) {
return AuthenticationState.SEND_FAILURE;
} else if (!requireAll && successes.isEmpty()) {
return AuthenticationState.SEND_FAILURE;
}
// Combine successful authentications
UserIdentity combinedUser = combineUserIdentities(successes);
return new LoginAuthenticator.UserAuthenticationSucceeded(getAuthenticationType(), combinedUser);
}
private UserIdentity combineUserIdentities(List<AuthenticationState.Succeeded> successes) {
// Logic to combine multiple user identities
// Could merge roles, use primary identity, etc.
AuthenticationState.Succeeded primary = successes.get(0);
return primary.getUserIdentity();
}
}public class AuthenticationErrorHandling {
public void handleAuthenticationErrors(Authenticator authenticator,
Request request, Response response, Callback callback) {
try {
AuthenticationState state = authenticator.validateRequest(request, response, callback);
if (state instanceof AuthenticationState.Succeeded) {
// Authentication successful
AuthenticationState.Succeeded success = (AuthenticationState.Succeeded) state;
logSuccessfulAuthentication(success);
} else if (state == AuthenticationState.CHALLENGE) {
// Challenge sent to client
logAuthenticationChallenge(request);
} else if (state == AuthenticationState.SEND_FAILURE) {
// Authentication failed
logAuthenticationFailure(request);
} else if (state instanceof AuthenticationState.Deferred) {
// Deferred authentication
logDeferredAuthentication(request);
}
} catch (ServerAuthException e) {
handleServerAuthException(e, request, response);
}
}
private void logSuccessfulAuthentication(AuthenticationState.Succeeded success) {
logger.info("Authentication successful: user={}, type={}",
success.getUserIdentity().getUserPrincipal().getName(),
success.getAuthenticationType());
}
private void logAuthenticationChallenge(Request request) {
logger.debug("Authentication challenge sent to client: {}",
Request.getRemoteAddr(request));
}
private void logAuthenticationFailure(Request request) {
logger.warn("Authentication failed for client: {}",
Request.getRemoteAddr(request));
}
private void logDeferredAuthentication(Request request) {
logger.debug("Authentication deferred for request: {}",
request.getHttpURI().getPath());
}
private void handleServerAuthException(ServerAuthException e, Request request, Response response) {
logger.error("Server authentication exception for client: {} - {}",
Request.getRemoteAddr(request), e.getMessage());
if (!response.isCommitted()) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
try {
response.getWriter().write("Authentication service unavailable");
} catch (IOException ioE) {
logger.error("Error writing authentication error response", ioE);
}
}
}
}public class AuthenticationOptimization {
// Cached authenticator for frequently accessed resources
public static class CachingAuthenticator implements Authenticator {
private final Authenticator delegate;
private final Cache<String, AuthenticationState> cache;
public CachingAuthenticator(Authenticator delegate, Duration cacheTTL) {
this.delegate = delegate;
this.cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(cacheTTL)
.build();
}
@Override
public void setConfiguration(Configuration configuration) {
delegate.setConfiguration(configuration);
}
@Override
public String getAuthenticationType() {
return delegate.getAuthenticationType();
}
@Override
public AuthenticationState validateRequest(Request request, Response response, Callback callback)
throws ServerAuthException {
String cacheKey = buildCacheKey(request);
if (cacheKey != null) {
AuthenticationState cached = cache.getIfPresent(cacheKey);
if (cached instanceof AuthenticationState.Succeeded) {
return cached;
}
}
AuthenticationState result = delegate.validateRequest(request, response, callback);
if (result instanceof AuthenticationState.Succeeded && cacheKey != null) {
cache.put(cacheKey, result);
}
return result;
}
private String buildCacheKey(Request request) {
// Build cache key from authentication credentials
String authHeader = request.getHeaders().get(HttpHeader.AUTHORIZATION);
return authHeader != null ? authHeader.hashCode() + "" : null;
}
}
}Authentication in Jetty Security provides flexible, secure, and performant solutions for various authentication requirements, from simple Basic auth to complex multi-factor scenarios.
Install with Tessl CLI
npx tessl i tessl/maven-org-eclipse-jetty--jetty-security