-
Notifications
You must be signed in to change notification settings - Fork 673
Support SPNEGO Authentication in HttpClient #3813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 1.2.x
Are you sure you want to change the base?
Conversation
I tested Kerberos authentication using the krb5 available at https://formulae.brew.sh/formula/krb5. |
a656f1d
to
8dd00a1
Compare
Signed-off-by: raccoonback <[email protected]>
19ccf13
to
090e1c2
Compare
5ce5819
to
a6efd89
Compare
Signed-off-by: raccoonback <[email protected]>
public class JaasAuthenticator implements SpnegoAuthenticator { | ||
|
||
private final String contextName; | ||
|
||
/** | ||
* Creates a new JaasAuthenticator with the given context name. | ||
* | ||
* @param contextName the JAAS login context name | ||
*/ | ||
public JaasAuthenticator(String contextName) { | ||
this.contextName = contextName; | ||
} | ||
|
||
/** | ||
* Performs a JAAS login using the configured context name and returns the authenticated Subject. | ||
* | ||
* @return the authenticated JAAS Subject | ||
* @throws LoginException if login fails | ||
*/ | ||
@Override | ||
public Subject login() throws LoginException { | ||
LoginContext context = new LoginContext(contextName); | ||
context.login(); | ||
return context.getSubject(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It logs in using the specified JAAS login context name and returns the authenticated Subject
object.
HttpClientOperations operations = connection.as(HttpClientOperations.class); | ||
if (operations != null && handler.spnegoAuthProvider != null) { | ||
int statusCode = operations.status().code(); | ||
HttpHeaders headers = operations.responseHeaders(); | ||
if (handler.spnegoAuthProvider.isUnauthorized(statusCode, headers)) { | ||
handler.spnegoAuthProvider.invalidateCache(); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a SPNEGO authentication expiration response is received via the HTTP response, the authentication header value is cleared.
if (spnegoAuthProvider != null) { | ||
return spnegoAuthProvider.apply(ch, ch.address()) | ||
.then( | ||
Mono.defer( | ||
() -> Mono.from(requestWithBodyInternal(ch)) | ||
) | ||
); | ||
} | ||
|
||
return requestWithBodyInternal(ch); | ||
} | ||
|
||
private Publisher<Void> requestWithBodyInternal(HttpClientOperations ch) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If SPNEGO authentication is configured through the HttpClient, authentication is attempted before sending the request.
if (verifiedAuthHeader != null) { | ||
request.header(HttpHeaderNames.AUTHORIZATION, verifiedAuthHeader); | ||
return Mono.empty(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a valid authentication token already exists, it is reused.
return Mono.fromCallable(() -> { | ||
try { | ||
return Subject.doAs( | ||
authenticator.login(), | ||
(PrivilegedAction<byte[]>) () -> { | ||
try { | ||
byte[] token = generateSpnegoToken(address.getHostName()); | ||
String authHeader = SPNEGO_HEADER + " " + Base64.getEncoder().encodeToString(token); | ||
|
||
verifiedAuthHeader = authHeader; | ||
request.header(HttpHeaderNames.AUTHORIZATION, authHeader); | ||
return token; | ||
} | ||
catch (GSSException e) { | ||
throw new RuntimeException("Failed to generate SPNEGO token", e); | ||
} | ||
} | ||
); | ||
} | ||
catch (LoginException e) { | ||
throw new RuntimeException("Failed to login with SPNEGO", e); | ||
} | ||
}) | ||
.subscribeOn(boundedElastic()) | ||
.then(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After obtaining Kerberos credentials via JAAS login, a SPNEGO token for the target server is generated using the GSS-API, and the Authorization: Negotiate <token>
header is added to the request.
Signed-off-by: raccoonback <[email protected]>
… authentication Signed-off-by: raccoonback <[email protected]>
Signed-off-by: raccoonback <[email protected]>
@@ -446,6 +447,16 @@ public Context currentContext() { | |||
@Override | |||
public void onStateChange(Connection connection, State newState) { | |||
if (newState == HttpClientState.RESPONSE_RECEIVED) { | |||
HttpClientOperations operations = connection.as(HttpClientOperations.class); | |||
if (operations != null && handler.spnegoAuthProvider != null) { | |||
if (shouldRetryWithSpnego(operations)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-authenticates with SPNEGO if necessary.
if (throwable instanceof SpnegoRetryException) { | ||
return true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Configures it to retry even if a SpnegoRetryException
occurs.
@violetagg |
This is so great! Looking forward to get this in :) |
Motivation
This PR adds support for SPNEGO (Kerberos) authentication to HttpClient, addressing #3079.
SPNEGO is widely used for HTTP authentication in enterprise environments, particularly those based on Kerberos.
Changes
SpnegoAuthProvider
Provides SPNEGO authentication by generating a Kerberos-based token and attaching it to the
Authorization
header of outgoing HTTP requests.JaasAuthenticator
Provides a pluggable way to perform JAAS-based Kerberos login, making it easy to integrate with various authentication backends.
HttpClient.spnego(...) API
Adds a new API to configure SPNEGO authentication for HttpClient instances.
jaas.conf
A JAAS(Java Authentication and Authorization Service) configuration file in Java for integrating with authentication backends such as Kerberos.
krb5.conf
krb5.conf is a Kerberos client configuration file used to define how the client locates and communicates with the Kerberos Key Distribution Center (KDC) for authentication.
How It Works
401 Unauthorized
and aWWW-Authenticate: Negotiate
header,the client automatically generates a SPNEGO token using the Kerberos ticket and resends the request with the appropriate
Authorization
header.Environment Configuration
Requires proper JAAS (jaas.conf) and Kerberos (krb5.conf) configuration.
See the updated documentation for example configuration files and JVM options.
Additional Notes
SpnegoAuthProvider
allows for easy extension and testing by supporting custom authenticators and GSSManager injection.