Eclipse Jetty HTTP Client - A lightweight, asynchronous HTTP client library that supports HTTP/1.1, HTTP/2, WebSocket, and various authentication mechanisms, proxy configurations, and connection pooling strategies.
—
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.
The central storage system for authentication credentials and results.
public interface AuthenticationStore {
// Authentication management
void addAuthentication(Authentication authentication);
void removeAuthentication(Authentication authentication);
void clearAuthentications();
Collection<Authentication> getAuthentications();
// Authentication result management
void addAuthenticationResult(Authentication.Result result);
void removeAuthenticationResult(Authentication.Result result);
void clearAuthenticationResults();
Authentication.Result findAuthenticationResult(URI uri);
}The base interface for all authentication mechanisms.
public interface Authentication {
// Authentication matching
boolean matches(String type, URI uri, String realm);
// Authentication execution
Authentication.Result authenticate(Request request, ContentResponse response,
Authentication.HeaderInfo headerInfo, Attributes context);
// Header information class
static class HeaderInfo {
String getType();
String getRealm();
String getBase64();
Map<String, String> getParameters();
String getParameter(String paramName);
}
}Represents the result of an authentication attempt.
public interface Authentication.Result {
URI getURI();
void apply(Request request);
}HTTP Basic authentication using username and password credentials.
public class BasicAuthentication implements Authentication {
public BasicAuthentication(URI uri, String realm, String user, String password);
public BasicAuthentication(String user, String password);
public String getUser();
public String getPassword();
}// Basic authentication for specific URI and realm
URI apiUri = URI.create("https://api.example.com");
BasicAuthentication auth = new BasicAuthentication(apiUri, "API Realm", "username", "password");
// Add to authentication store
client.getAuthenticationStore().addAuthentication(auth);
// Make authenticated request
ContentResponse response = client.GET("https://api.example.com/protected");
// Global basic authentication (matches any realm)
BasicAuthentication globalAuth = new BasicAuthentication("admin", "secret123");
client.getAuthenticationStore().addAuthentication(globalAuth);// Add authentication result directly to avoid initial challenge
URI uri = URI.create("https://api.example.com");
String credentials = Base64.getEncoder().encodeToString("user:pass".getBytes());
AuthenticationResult result = AuthenticationResult.from(uri, null, "Authorization", "Basic " + credentials);
client.getAuthenticationStore().addAuthenticationResult(result);
// First request will include Authorization header immediately
ContentResponse response = client.GET("https://api.example.com/data");HTTP Digest authentication providing improved security over Basic authentication.
public class DigestAuthentication implements Authentication {
public DigestAuthentication(URI uri, String realm, String user, String password);
public String getUser();
public String getPassword();
}// Digest authentication setup
URI secureUri = URI.create("https://secure.example.com");
DigestAuthentication digestAuth = new DigestAuthentication(
secureUri,
"Secure Area",
"username",
"password"
);
client.getAuthenticationStore().addAuthentication(digestAuth);
// Make request - digest challenge will be handled automatically
ContentResponse response = client.GET("https://secure.example.com/data");// The client automatically handles the digest authentication flow:
// 1. Initial request without authentication
// 2. Server responds with 401 and WWW-Authenticate header
// 3. Client calculates digest response using provided nonce
// 4. Client retries request with Authorization header
// 5. Server validates digest and responds with requested content
DigestAuthentication auth = new DigestAuthentication(
URI.create("https://api.example.com"),
"Protected",
"user",
"pass"
);
client.getAuthenticationStore().addAuthentication(auth);
// This request will automatically handle the digest challenge
ContentResponse response = client.GET("https://api.example.com/protected-resource");SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication for Kerberos/Windows authentication.
public class SPNEGOAuthentication implements Authentication {
public SPNEGOAuthentication(String serviceName);
public String getServiceName();
}// SPNEGO authentication for Kerberos
SPNEGOAuthentication spnegoAuth = new SPNEGOAuthentication("HTTP/server.example.com");
client.getAuthenticationStore().addAuthentication(spnegoAuth);
// SPNEGO authentication requires proper Kerberos configuration
// System properties or JAAS configuration may be needed
System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf");
System.setProperty("java.security.krb5.conf", "/path/to/krb5.conf");
ContentResponse response = client.GET("https://server.example.com/protected");// Example JAAS configuration (jaas.conf)
/*
Client {
com.sun.security.auth.module.Krb5LoginModule required
useTicketCache=true
renewTGT=true
doNotPrompt=true;
};
*/
// Example Kerberos configuration (krb5.conf)
/*
[libdefaults]
default_realm = EXAMPLE.COM
[realms]
EXAMPLE.COM = {
kdc = kdc.example.com
admin_server = admin.example.com
}
*/
public class KerberosClient {
public void configureSpnego() {
// Set system properties for Kerberos
System.setProperty("java.security.auth.login.config", "jaas.conf");
System.setProperty("java.security.krb5.conf", "krb5.conf");
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
// Create SPNEGO authentication
SPNEGOAuthentication auth = new SPNEGOAuthentication("HTTP/api.example.com");
client.getAuthenticationStore().addAuthentication(auth);
}
}AuthenticationStore authStore = client.getAuthenticationStore();
// Add multiple authentication mechanisms
authStore.addAuthentication(new BasicAuthentication("user1", "pass1"));
authStore.addAuthentication(new DigestAuthentication(
URI.create("https://secure.example.com"),
"Secure",
"user2",
"pass2"
));
authStore.addAuthentication(new SPNEGOAuthentication("HTTP/kerberos.example.com"));
// The client will automatically select the appropriate authentication
// based on server challengesAuthenticationStore authStore = client.getAuthenticationStore();
// Find existing authentication result
URI uri = URI.create("https://api.example.com");
AuthenticationResult existingResult = authStore.findAuthenticationResult(uri);
if (existingResult != null) {
System.out.println("Found cached authentication for: " + uri);
} else {
// Add preemptive authentication
AuthenticationResult result = AuthenticationResult.from(
uri,
"API",
"Authorization",
"Bearer " + accessToken
);
authStore.addAuthenticationResult(result);
}
// Clear authentication results when tokens expire
authStore.clearAuthenticationResults();Implement custom authentication mechanisms by extending the Authentication interface.
public class BearerTokenAuthentication implements Authentication {
private final URI uri;
private final String token;
public BearerTokenAuthentication(URI uri, String token) {
this.uri = uri;
this.token = token;
}
@Override
public String getType() {
return "Bearer";
}
@Override
public URI getURI() {
return uri;
}
@Override
public String getRealm() {
return null; // No realm for bearer tokens
}
@Override
public boolean matches(String type, URI uri, String realm) {
return "Bearer".equalsIgnoreCase(type) &&
(this.uri == null || this.uri.equals(uri));
}
@Override
public AuthenticationResult authenticate(Request request, ContentResponse response,
HeaderInfo headerInfo, Context context) {
return AuthenticationResult.from(
context.getURI(),
context.getRealm(),
"Authorization",
"Bearer " + token
);
}
}
// Usage
BearerTokenAuthentication tokenAuth = new BearerTokenAuthentication(
URI.create("https://api.example.com"),
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
);
client.getAuthenticationStore().addAuthentication(tokenAuth);public class ApiKeyAuthentication implements Authentication {
private final URI uri;
private final String apiKey;
private final String headerName;
public ApiKeyAuthentication(URI uri, String headerName, String apiKey) {
this.uri = uri;
this.headerName = headerName;
this.apiKey = apiKey;
}
@Override
public String getType() {
return "ApiKey";
}
@Override
public URI getURI() {
return uri;
}
@Override
public String getRealm() {
return null;
}
@Override
public boolean matches(String type, URI uri, String realm) {
return this.uri.getHost().equals(uri.getHost());
}
@Override
public AuthenticationResult authenticate(Request request, ContentResponse response,
HeaderInfo headerInfo, Context context) {
return AuthenticationResult.from(
context.getURI(),
context.getRealm(),
headerName,
apiKey
);
}
}
// Usage
ApiKeyAuthentication apiKeyAuth = new ApiKeyAuthentication(
URI.create("https://api.example.com"),
"X-API-Key",
"your-api-key-here"
);
client.getAuthenticationStore().addAuthentication(apiKeyAuth);Override authentication for specific requests without modifying the global authentication store.
// Override authentication for a single request
ContentResponse response = client.newRequest("https://api.example.com/data")
.header("Authorization", "Bearer " + specificToken)
.send();
// Use different API key for specific request
ContentResponse response2 = client.newRequest("https://different-api.com/data")
.header("X-API-Key", "different-api-key")
.send();public class TemporaryAuth {
private final HttpClient client;
private final AuthenticationStore originalStore;
public TemporaryAuth(HttpClient client) {
this.client = client;
this.originalStore = client.getAuthenticationStore();
}
public ContentResponse requestWithAuth(String url, Authentication auth) throws Exception {
// Create temporary authentication store
AuthenticationStore tempStore = new HttpAuthenticationStore();
tempStore.addAuthentication(auth);
// Temporarily replace authentication store
client.setAuthenticationStore(tempStore);
try {
return client.GET(url);
} finally {
// Restore original authentication store
client.setAuthenticationStore(originalStore);
}
}
}client.newRequest("https://api.example.com/protected")
.send(result -> {
if (result.isSucceeded()) {
Response response = result.getResponse();
if (response.getStatus() == 401) {
System.err.println("Authentication failed");
// Check WWW-Authenticate header for challenge details
String wwwAuth = response.getHeaders().get("WWW-Authenticate");
System.err.println("Challenge: " + wwwAuth);
// Handle specific authentication errors
if (wwwAuth != null && wwwAuth.contains("expired_token")) {
refreshToken();
}
}
} else {
Throwable failure = result.getFailure();
if (failure instanceof HttpResponseException) {
HttpResponseException httpEx = (HttpResponseException) failure;
if (httpEx.getResponse().getStatus() == 401) {
System.err.println("Authentication challenge failed");
}
}
}
});public class AuthenticatedClient {
private final HttpClient client;
private String accessToken;
public ContentResponse authenticatedRequest(String url) throws Exception {
// First attempt with current token
ContentResponse response = client.newRequest(url)
.header("Authorization", "Bearer " + accessToken)
.send();
if (response.getStatus() == 401) {
// Token expired, refresh and retry
refreshAccessToken();
response = client.newRequest(url)
.header("Authorization", "Bearer " + accessToken)
.send();
if (response.getStatus() == 401) {
throw new SecurityException("Authentication failed after token refresh");
}
}
return response;
}
private void refreshAccessToken() {
// Implement token refresh logic
// This could involve calling a refresh token endpoint
// or re-authenticating with username/password
}
}// Avoid hardcoding credentials
public class SecureAuthConfig {
public static BasicAuthentication createFromEnvironment() {
String username = System.getenv("API_USERNAME");
String password = System.getenv("API_PASSWORD");
if (username == null || password == null) {
throw new IllegalStateException("Authentication credentials not configured");
}
return new BasicAuthentication(username, password);
}
public static BearerTokenAuthentication createFromTokenFile(Path tokenFile) throws IOException {
String token = Files.readString(tokenFile).trim();
return new BearerTokenAuthentication(null, token);
}
}public class LoggingAuthenticationStore implements AuthenticationStore {
private final AuthenticationStore delegate;
private final Logger logger;
@Override
public void addAuthentication(Authentication authentication) {
logger.info("Adding authentication for: {} - {}",
authentication.getType(),
authentication.getURI());
delegate.addAuthentication(authentication);
}
@Override
public AuthenticationResult findAuthenticationResult(URI uri) {
AuthenticationResult result = delegate.findAuthenticationResult(uri);
if (result != null) {
logger.debug("Found cached authentication result for: {}", uri);
} else {
logger.debug("No cached authentication result for: {}", uri);
}
return result;
}
// Implement other methods...
}
// Usage
LoggingAuthenticationStore loggingStore = new LoggingAuthenticationStore(
client.getAuthenticationStore(),
LoggerFactory.getLogger("auth")
);Install with Tessl CLI
npx tessl i tessl/maven-org-eclipse-jetty--jetty-client