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())); + } }