This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new c461b20fb Support preemptive authentication with Java HTTP Client
c461b20fb is described below

commit c461b20fbe6b6471f1272c5c4101b293e3dd8a5f
Author: Konrad Windszus <k...@apache.org>
AuthorDate: Thu Oct 9 18:13:38 2025 +0200

    Support preemptive authentication with Java HTTP Client
    
    This closes #1622
---
 .../aether/transport/jdk/JdkTransporter.java       | 47 ++++++++++++++++++++--
 .../aether/transport/jdk/JdkTransporterTest.java   | 34 +++++++++++++---
 2 files changed, 71 insertions(+), 10 deletions(-)

diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
index 7029762e8..2eab4a888 100644
--- 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
+++ 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java
@@ -52,6 +52,7 @@ import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
+import java.util.Base64;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Locale;
@@ -79,6 +80,7 @@ import org.eclipse.aether.util.ConfigUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
 import static 
org.eclipse.aether.spi.connector.transport.http.HttpConstants.ACCEPT_ENCODING;
 import static 
org.eclipse.aether.spi.connector.transport.http.HttpConstants.CACHE_CONTROL;
 import static 
org.eclipse.aether.spi.connector.transport.http.HttpConstants.CONTENT_LENGTH;
@@ -135,6 +137,12 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
 
     private final Semaphore maxConcurrentRequests;
 
+    private boolean preemptivePutAuth;
+
+    private boolean preemptiveAuth;
+
+    private PasswordAuthentication serverAuthentication;
+
     JdkTransporter(
             RepositorySystemSession session,
             RemoteRepository repository,
@@ -227,6 +235,17 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
                 CONFIG_PROP_MAX_CONCURRENT_REQUESTS + "." + repository.getId(),
                 CONFIG_PROP_MAX_CONCURRENT_REQUESTS));
 
+        this.preemptiveAuth = ConfigUtils.getBoolean(
+                session,
+                ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_AUTH,
+                ConfigurationProperties.HTTP_PREEMPTIVE_AUTH + "." + 
repository.getId(),
+                ConfigurationProperties.HTTP_PREEMPTIVE_AUTH);
+        this.preemptivePutAuth = ConfigUtils.getBoolean(
+                session,
+                ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_PUT_AUTH,
+                ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH + "." + 
repository.getId(),
+                ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH);
+
         this.headers = headers;
         this.client = createClient(session, repository, insecure);
     }
@@ -255,6 +274,8 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
         HttpRequest.Builder request =
                 HttpRequest.newBuilder().uri(resolve(task)).method("HEAD", 
HttpRequest.BodyPublishers.noBody());
         headers.forEach(request::setHeader);
+
+        prepare(request);
         try {
             HttpResponse<Void> response = send(request.build(), 
HttpResponse.BodyHandlers.discarding());
             if (response.statusCode() >= MULTIPLE_CHOICES) {
@@ -286,6 +307,7 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
                     request.header(ACCEPT_ENCODING, "identity");
                 }
 
+                prepare(request);
                 try {
                     response = send(request.build(), 
HttpResponse.BodyHandlers.ofInputStream());
                     if (response.statusCode() >= MULTIPLE_CHOICES) {
@@ -398,6 +420,7 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
             utilPut(task, Files.newOutputStream(tempFile.getPath()), true);
             request.PUT(HttpRequest.BodyPublishers.ofFile(tempFile.getPath()));
 
+            prepare(request);
             try {
                 HttpResponse<Void> response = send(request.build(), 
HttpResponse.BodyHandlers.discarding());
                 if (response.statusCode() >= MULTIPLE_CHOICES) {
@@ -409,6 +432,24 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
         }
     }
 
+    private void prepare(HttpRequest.Builder requestBuilder) {
+        if (serverAuthentication != null
+                && (preemptiveAuth
+                        || (preemptivePutAuth && 
requestBuilder.build().method().equals("PUT")))) {
+            // https://stackoverflow.com/a/58612586
+            requestBuilder.setHeader(
+                    "Authorization",
+                    getBasicAuthValue(serverAuthentication.getUserName(), 
serverAuthentication.getPassword()));
+        }
+    }
+
+    static String getBasicAuthValue(String username, char[] password) {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append(username).append(':').append(password);
+        // Java's HTTP client uses ISO-8859-1 for Basic auth encoding
+        return "Basic " + 
Base64.getEncoder().encodeToString(sb.toString().getBytes(ISO_8859_1));
+    }
+
     private <T> HttpResponse<T> send(HttpRequest request, 
HttpResponse.BodyHandler<T> responseBodyHandler)
             throws Exception {
         maxConcurrentRequests.acquire();
@@ -437,10 +478,8 @@ final class JdkTransporter extends AbstractTransporter 
implements HttpTransporte
 
                 String username = 
repoAuthContext.get(AuthenticationContext.USERNAME);
                 String password = 
repoAuthContext.get(AuthenticationContext.PASSWORD);
-
-                authentications.put(
-                        Authenticator.RequestorType.SERVER,
-                        new PasswordAuthentication(username, 
password.toCharArray()));
+                serverAuthentication = new PasswordAuthentication(username, 
password.toCharArray());
+                authentications.put(Authenticator.RequestorType.SERVER, 
serverAuthentication);
             }
         }
 
diff --git 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java
 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java
index aaa168296..1f2976072 100644
--- 
a/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java
+++ 
b/maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java
@@ -29,7 +29,10 @@ import org.eclipse.aether.spi.connector.transport.PeekTask;
 import org.eclipse.aether.spi.connector.transport.Transporter;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnJre;
+import org.junit.jupiter.api.condition.JRE;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -50,14 +53,22 @@ class JdkTransporterTest extends HttpTransporterTest {
     protected void testPut_ProxyUnauthenticated() {}
 
     @Override
-    @Disabled
+    @DisabledOnJre(
+            value = {JRE.JAVA_17, JRE.JAVA_21},
+            disabledReason = "JDK-8326949")
     @Test
-    protected void testAuthSchemePreemptive() {}
+    protected void testAuthSchemePreemptive() throws Exception {
+        super.testAuthSchemePreemptive();
+    }
 
     @Override
-    @Disabled
+    @DisabledOnJre(
+            value = {JRE.JAVA_17, JRE.JAVA_21},
+            disabledReason = "JDK-8326949")
     @Test
-    protected void testPut_AuthCache_Preemptive() {}
+    protected void testPut_AuthCache_Preemptive() throws Exception {
+        super.testPut_AuthCache_Preemptive();
+    }
 
     @Override
     @Disabled
@@ -65,9 +76,13 @@ class JdkTransporterTest extends HttpTransporterTest {
     protected void testPut_Unauthenticated() {}
 
     @Override
-    @Disabled
+    @DisabledOnJre(
+            value = {JRE.JAVA_17, JRE.JAVA_21},
+            disabledReason = "JDK-8326949")
     @Test
-    protected void testPut_PreemptiveIsDefault() {}
+    protected void testPut_PreemptiveIsDefault() throws Exception {
+        super.testPut_PreemptiveIsDefault();
+    }
 
     @Override
     @Disabled
@@ -113,4 +128,11 @@ class JdkTransporterTest extends HttpTransporterTest {
             fail("We expect ConnectException");
         }
     }
+
+    @Test
+    void testGetBasicAuthValue() {
+        assertEquals(
+                "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
+                JdkTransporter.getBasicAuthValue("Aladdin", "open 
sesame".toCharArray()));
+    }
 }

Reply via email to