Skip to content

Commit a417bb0

Browse files
committed
Generalize the httpAuthentication infrastructure and remove SPNEGO-specific design
Following the review feedback, this change updates the proposed SPNEGO-specific implementation into a more generic authentication flow that can support various HTTP authentication mechanisms. Signed-off-by: raccoonback <[email protected]>
1 parent 5eaf4f0 commit a417bb0

File tree

18 files changed

+624
-1139
lines changed

18 files changed

+624
-1139
lines changed

CLAUDE.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Reactor-Netty is a reactive networking library providing non-blocking TCP/HTTP/UDP/QUIC clients and servers based on Netty. It's part of the Project Reactor ecosystem and requires Java 8+ to run.
8+
9+
## Build System
10+
11+
**Gradle-based** multi-module project using Gradle 8.14.2:
12+
13+
```bash
14+
# Build and test
15+
./gradlew build # Build all modules
16+
./gradlew check # Run all checks including tests
17+
./gradlew test # Run tests
18+
./gradlew clean # Clean build artifacts
19+
20+
# Module-specific testing
21+
./gradlew reactor-netty-core:test # Test core module
22+
./gradlew reactor-netty-http:test # Test HTTP module
23+
24+
# Code quality
25+
./gradlew spotlessCheck # Check code formatting
26+
./gradlew spotlessApply # Apply code formatting
27+
./gradlew checkstyle # Run checkstyle
28+
29+
# Documentation
30+
./gradlew javadoc # Generate Javadoc
31+
./gradlew antora # Build Antora documentation
32+
33+
# Publishing
34+
./gradlew publishToMavenLocal # Publish to local Maven repository
35+
```
36+
37+
## Architecture
38+
39+
### Core Modules
40+
41+
- **`reactor-netty-core`**: Core networking functionality, connection management, metrics
42+
- **`reactor-netty-http`**: HTTP client/server with HTTP/2 and HTTP/3 support
43+
- **`reactor-netty-http-brave`**: Brave tracing integration
44+
- **`reactor-netty-incubator-quic`**: QUIC protocol support (experimental)
45+
- **`reactor-netty-examples`**: Example applications
46+
47+
### Key Source Structure
48+
49+
**reactor-netty-core/src/main/java/reactor/netty/**:
50+
- **Core APIs**: `Connection`, `ByteBufFlux`, `ByteBufMono`, `ReactorNetty`
51+
- **channel/**: Channel operations, metrics, observations
52+
- **resources/**: Connection providers, event loops, pooling
53+
- **tcp/**: TCP client/server implementations and SSL support
54+
- **transport/**: Transport abstractions, address resolution, logging
55+
- **udp/**: UDP client/server implementations
56+
57+
**reactor-netty-http/src/main/java/reactor/netty/http/**:
58+
- **client/**: HTTP client with HTTP/2, HTTP/3, WebSocket support
59+
- **server/**: HTTP server with routing, compression, observability
60+
- **websocket/**: WebSocket client/server functionality
61+
62+
## Testing
63+
64+
Multiple test sourcesets for comprehensive coverage:
65+
- **Regular tests**: `src/test/java/`
66+
- **Context propagation tests**: `src/contextPropagationTest/`
67+
- **HTTP/3 tests**: `src/http3Test/`
68+
- **No-micrometer tests**: `src/noMicrometerTest/`
69+
70+
## Key Technologies
71+
72+
- **Netty 4.1.122.Final**: Core networking framework
73+
- **Project Reactor 3.7.8-SNAPSHOT**: Reactive streams
74+
- **Micrometer 1.14.0**: Metrics and observability
75+
- **Multi-release JARs**: Java 8/17 compatibility using `me.champeau.mrjar` plugin
76+
77+
## Development Notes
78+
79+
- Uses **shadowed JARs** in core module for dependency isolation
80+
- **Native transports**: Epoll (Linux), KQueue (macOS), io_uring (Linux)
81+
- **Observability**: Comprehensive metrics, tracing, and context propagation
82+
- **API compatibility**: JAPICMP checks for API compatibility
83+
- **Documentation**: Antora-based AsciiDoc documentation in `/docs/`

docs/modules/ROOT/pages/http-client.adoc

Lines changed: 80 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -743,143 +743,105 @@ include::{examples-dir}/resolver/Application.java[lines=18..39]
743743
----
744744
<1> The timeout of each DNS query performed by this resolver will be 500ms.
745745

746-
[[http-client-spnego]]
747-
== SPNEGO Authentication
748-
Reactor Netty HttpClient supports SPNEGO (Kerberos) authentication, which is widely used in enterprise environments.
749-
SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) provides secure authentication over HTTP using Kerberos.
746+
[[http-authentication]]
747+
== HTTP Authentication
748+
Reactor Netty `HttpClient` provides a flexible HTTP authentication framework that allows you to implement
749+
custom authentication mechanisms such as SPNEGO/Negotiate, OAuth, Bearer tokens, or any other HTTP-based authentication scheme.
750750

751-
==== How It Works
752-
SPNEGO authentication follows this HTTP authentication flow:
751+
The {javadoc}/reactor/netty/http/client/HttpClient.html#httpAuthentication-java.util.function.BiPredicate-java.util.function.BiFunction-[`httpAuthentication`]
752+
method accepts two parameters:
753753

754-
. The client sends an HTTP request to a protected resource.
755-
. The server responds with `401 Unauthorized` and a `WWW-Authenticate: Negotiate` header.
756-
. The client generates a SPNEGO token based on its Kerberos ticket, and resends the request with an `Authorization: Negotiate <base64-encoded-token>` header.
757-
. The server validates the token and, if authentication is successful, returns 200 OK.
758-
759-
If further negotiation is required, the server may return another 401 with additional data in the `WWW-Authenticate` header.
754+
* A predicate that determines when authentication should be applied (typically by checking the HTTP status code and headers)
755+
* An authenticator function that applies authentication credentials to the request
760756

761-
==== JAAS-based Authenticator
762-
{examples-link}/spnego/jaas/Application.java
763-
----
764-
include::{examples-dir}/spnego/jaas/Application.java[lines=18..45]
765-
----
766-
<1> Configures the `jaas.conf`. A JAAS configuration file in Java for integrating with authentication backends such as Kerberos.
767-
<2> Configures the `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.
768-
<3> Configures the SPNEGO jaas.conf. A JVM system property that enables detailed debug logging for Kerberos operations in Java.
769-
<4> `JaasAuthenticator` performs Kerberos authentication using a JAAS configuration (jaas.conf).
770-
<5> `SpnegoAuthProvider.Builder` supports the following configuration methods. Please refer to <<spnegoauthprovider-config>>.
771-
<6> `SpnegoAuthProvider` generates a SPNEGO token from the Kerberos ticket. It automatically adds the `Authorization: Negotiate ...` header to HTTP requests. If the server responds with `401 Unauthorized` and includes `WWW-Authenticate: Negotiate`, the client will automatically reauthenticate and retry the request once.
757+
This approach gives you complete control over the authentication flow while Reactor Netty handles the retry mechanism.
772758

773-
===== Example JAAS Configuration
774-
Specify the path to your JAAS configuration file using the `java.security.auth.login.config` system property.
775-
776-
.`jaas.conf`
777-
[jaas,conf]
778-
----
779-
KerberosLogin {
780-
com.sun.security.auth.module.Krb5LoginModule required
781-
client=true
782-
useKeyTab=true
783-
keyTab="/path/to/test.keytab"
784-
principal="[email protected]"
785-
doNotPrompt=true
786-
debug=true;
787-
};
788-
----
759+
=== How It Works
789760

790-
===== Example Kerberos Configuration
791-
Specify Kerberos realm and KDC information using the `java.security.krb5.conf` system property.
761+
The typical HTTP authentication flow works as follows:
792762

793-
.`krb5.conf`
794-
[krb5,conf]
795-
----
796-
[libdefaults]
797-
default_realm = EXAMPLE.COM
798-
[realms]
799-
EXAMPLE.COM = {
800-
kdc = kdc.example.com
801-
}
802-
[domain_realms]
803-
.example.com = EXAMPLE.COM
804-
example.com = EXAMPLE.COM
805-
----
763+
. The client sends an HTTP request to a protected resource.
764+
. The server responds with an authentication challenge (e.g., `401 Unauthorized` with a `WWW-Authenticate` header).
765+
. The authenticator function is invoked to add authentication credentials to the request.
766+
. The request is retried with the authentication credentials.
767+
. If authentication is successful, the server returns the requested resource.
806768

807-
===== Configuration Example
808-
[jvm option]
809-
----
810-
-Djava.security.auth.login.config=/path/to/login.conf
811-
-Djava.security.krb5.conf=/path/to/krb5.conf
812-
----
769+
=== Token-Based Authentication Example
813770

814-
==== GSSCredential-based Authenticator
815-
For scenarios where you already have a `GSSCredential` available or want to avoid JAAS configuration, you can use `GssCredentialAuthenticator`:
771+
The following example demonstrates how to implement Bearer token authentication:
816772

817-
{examples-link}/spnego/gsscredential/Application.java
773+
{examples-link}/authentication/token/Application.java
774+
[%unbreakable]
818775
----
819-
include::{examples-dir}/spnego/gsscredential/Application.java[lines=18..46]
776+
include::{examples-dir}/authentication/token/Application.java[lines=18..52]
820777
----
821-
<1> Obtain the `GSSCredential` through other means.
822-
<2> Configure the GSSCredential-based authenticator for SPNEGO authentication.
778+
<1> The predicate checks if the response status is `401 Unauthorized`.
779+
<2> The authenticator adds the `Authorization` header with a Bearer token.
823780

824-
This approach is useful when:
825-
- You want to reuse existing credentials
826-
- You need more control over credential management
827-
- JAAS configuration is not available or preferred
781+
=== SPNEGO/Negotiate Authentication Example
828782

829-
==== Custom Authenticator Implementation
830-
For advanced scenarios where the provided authenticators don't meet your specific requirements, you can implement the `SpnegoAuthenticator` interface directly:
783+
For SPNEGO (Kerberos) authentication, you can implement a custom authenticator using Java's GSS-API:
831784

785+
{examples-link}/authentication/spnego/Application.java
786+
[%unbreakable]
787+
----
788+
include::{examples-dir}/authentication/spnego/Application.java[lines=18..69]
832789
----
833-
import org.ietf.jgss.GSSContext;
834-
import reactor.netty.http.client.SpnegoAuthenticator;
835-
import reactor.netty.http.client.SpnegoAuthProvider;
790+
<1> The predicate checks for `401 Unauthorized` with `WWW-Authenticate: Negotiate` header.
791+
<2> The authenticator generates a SPNEGO token using GSS-API and adds it to the `Authorization` header.
836792

837-
public class CustomSpnegoAuthenticator implements SpnegoAuthenticator {
793+
NOTE: For SPNEGO authentication, you need to configure Kerberos settings (e.g., `krb5.conf`) and JAAS configuration
794+
(e.g., `jaas.conf`) appropriately. Set the system properties `java.security.krb5.conf` and `java.security.auth.login.config`
795+
to point to your configuration files.
838796

839-
@Override
840-
public GSSContext createContext(String serviceName, String remoteHost) throws Exception {
841-
// Your custom authentication logic here
842-
// This method should return a configured GSSContext
843-
// for the specified service and remote host
844-
// serviceName: e.g., "HTTP", "LDAP"
845-
// remoteHost: target server hostname
797+
=== Custom Authentication Scenarios
846798

847-
return null; // Replace with actual GSSContext creation logic
848-
}
849-
}
799+
The `httpAuthentication` method is flexible enough to support various authentication scenarios:
850800

851-
// Usage with advanced configuration
801+
==== OAuth 2.0 Authentication
802+
[source,java]
803+
----
852804
HttpClient client = HttpClient.create()
853-
.spnego(
854-
SpnegoAuthProvider.builder(new CustomSpnegoAuthenticator())
855-
.serviceName("HTTP") // Custom service name
856-
.unauthorizedStatusCode(401) // Custom status code
857-
.resolveCanonicalHostname(true) // Use canonical hostname
858-
.build()
859-
);
805+
.httpAuthentication(
806+
(req, res) -> res.status().code() == 401,
807+
(req, addr) -> {
808+
return fetchOAuthToken() // <1>
809+
.doOnNext(token ->
810+
req.header(HttpHeaderNames.AUTHORIZATION, "Bearer " + token))
811+
.then();
812+
}
813+
);
814+
----
815+
<1> Asynchronously fetch an OAuth token and add it to the request.
816+
817+
==== Basic Authentication
818+
{examples-link}/authentication/basic/Application.java
819+
[%unbreakable]
860820
----
821+
include::{examples-dir}/authentication/basic/Application.java[lines=18..46]
822+
----
823+
<1> The predicate checks if the response status is `401 Unauthorized`.
824+
<2> The authenticator adds Basic authentication credentials to the `Authorization` header.
861825

862-
This approach is useful when you need:
863-
- Custom credential acquisition logic
864-
- Integration with third-party authentication systems
865-
- Special handling for token caching or refresh
866-
- Environment-specific authentication flows
867-
868-
[[spnegoauthprovider-config]]
869-
==== SpnegoAuthProvider Configuration Options
870-
The `SpnegoAuthProvider.Builder` supports the following configuration Options:
871-
872-
[width="100%",options="header"]
873-
|=======
874-
| Method | Default | Description | Example
875-
| `serviceName(String)` | "HTTP" | Service name for constructing service principal names (serviceName/hostname) | "HTTP", "LDAP"
876-
| `unauthorizedStatusCode(int)` | 401 | HTTP status code that triggers authentication retry | 401, 407
877-
| `resolveCanonicalHostname(boolean)` | false | Whether to use canonical hostname resolution via reverse DNS lookup | true for FQDN requirements
878-
|=======
879-
880-
==== Notes
881-
- SPNEGO authentication is fully supported on Java 1.6 and above.
882-
- If authentication fails, check the server logs and client exception messages, and verify your Kerberos environment settings (realm, KDC, ticket, etc.).
883-
- `JaasAuthenticator` performs authentication through JAAS login configuration.
884-
- `GssCredentialAuthenticator` uses pre-existing `GSSCredential` objects, bypassing JAAS configuration.
885-
- For custom scenarios, implement the `SpnegoAuthenticator` interface to provide your own authentication logic.
826+
==== Proxy Authentication
827+
[source,java]
828+
----
829+
HttpClient client = HttpClient.create()
830+
.httpAuthentication(
831+
(req, res) -> res.status().code() == 407, // <1>
832+
(req, addr) -> {
833+
String proxyCredentials = generateProxyCredentials();
834+
req.header("Proxy-Authorization", "Bearer " + proxyCredentials);
835+
return Mono.empty();
836+
}
837+
);
838+
----
839+
<1> Check for `407 Proxy Authentication Required` status code.
840+
841+
=== Important Notes
842+
843+
* The authenticator function is invoked only when the predicate returns `true`.
844+
* The authenticator receives the request and remote address, allowing you to customize authentication based on the target server.
845+
* The authenticator returns a `Mono<Void>` which allows for asynchronous credential retrieval.
846+
* Authentication is retried only once per request. If authentication fails after retry, the error is propagated to the caller.
847+
* For security reasons, ensure that sensitive credentials are not logged or exposed in error messages.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (c) 2025 VMware, Inc. or its affiliates, All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package reactor.netty.examples.documentation.http.client.authentication.basic;
17+
18+
import io.netty.handler.codec.http.HttpHeaderNames;
19+
import reactor.core.publisher.Mono;
20+
import reactor.netty.http.client.HttpClient;
21+
22+
import java.nio.charset.StandardCharsets;
23+
import java.util.Base64;
24+
25+
public class Application {
26+
27+
public static void main(String[] args) {
28+
HttpClient client =
29+
HttpClient.create()
30+
.httpAuthentication(
31+
(req, res) -> res.status().code() == 401, // <1>
32+
(req, addr) -> { // <2>
33+
String credentials = "username:password";
34+
String encodedCredentials = Base64.getEncoder()
35+
.encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
36+
req.header(HttpHeaderNames.AUTHORIZATION, "Basic " + encodedCredentials);
37+
return Mono.empty();
38+
}
39+
);
40+
41+
client.get()
42+
.uri("https://example.com/")
43+
.response()
44+
.block();
45+
}
46+
}

0 commit comments

Comments
 (0)