Skip to content

Commit fd754c2

Browse files
committed
Support preemptive authentication with Java HTTP Client
This closes #1622
1 parent 7e442f3 commit fd754c2

File tree

2 files changed

+71
-13
lines changed

2 files changed

+71
-13
lines changed

maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import java.time.ZonedDateTime;
5353
import java.time.format.DateTimeFormatter;
5454
import java.time.format.DateTimeParseException;
55+
import java.util.Base64;
5556
import java.util.Collections;
5657
import java.util.HashMap;
5758
import java.util.Locale;
@@ -79,6 +80,7 @@
7980
import org.slf4j.Logger;
8081
import org.slf4j.LoggerFactory;
8182

83+
import static java.nio.charset.StandardCharsets.ISO_8859_1;
8284
import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.ACCEPT_ENCODING;
8385
import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.CACHE_CONTROL;
8486
import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.CONTENT_LENGTH;
@@ -135,6 +137,12 @@ final class JdkTransporter extends AbstractTransporter implements HttpTransporte
135137

136138
private final Semaphore maxConcurrentRequests;
137139

140+
private boolean preemptivePutAuth;
141+
142+
private boolean preemptiveAuth;
143+
144+
private PasswordAuthentication serverAuthentication;
145+
138146
JdkTransporter(
139147
RepositorySystemSession session,
140148
RemoteRepository repository,
@@ -227,6 +235,17 @@ final class JdkTransporter extends AbstractTransporter implements HttpTransporte
227235
CONFIG_PROP_MAX_CONCURRENT_REQUESTS + "." + repository.getId(),
228236
CONFIG_PROP_MAX_CONCURRENT_REQUESTS));
229237

238+
this.preemptiveAuth = ConfigUtils.getBoolean(
239+
session,
240+
ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_AUTH,
241+
ConfigurationProperties.HTTP_PREEMPTIVE_AUTH + "." + repository.getId(),
242+
ConfigurationProperties.HTTP_PREEMPTIVE_AUTH);
243+
this.preemptivePutAuth = ConfigUtils.getBoolean(
244+
session,
245+
ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_PUT_AUTH,
246+
ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH + "." + repository.getId(),
247+
ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH);
248+
230249
this.headers = headers;
231250
this.client = createClient(session, repository, insecure);
232251
}
@@ -255,6 +274,8 @@ protected void implPeek(PeekTask task) throws Exception {
255274
HttpRequest.Builder request =
256275
HttpRequest.newBuilder().uri(resolve(task)).method("HEAD", HttpRequest.BodyPublishers.noBody());
257276
headers.forEach(request::setHeader);
277+
278+
prepare(request);
258279
try {
259280
HttpResponse<Void> response = send(request.build(), HttpResponse.BodyHandlers.discarding());
260281
if (response.statusCode() >= MULTIPLE_CHOICES) {
@@ -286,6 +307,7 @@ protected void implGet(GetTask task) throws Exception {
286307
request.header(ACCEPT_ENCODING, "identity");
287308
}
288309

310+
prepare(request);
289311
try {
290312
response = send(request.build(), HttpResponse.BodyHandlers.ofInputStream());
291313
if (response.statusCode() >= MULTIPLE_CHOICES) {
@@ -397,7 +419,7 @@ protected void implPut(PutTask task) throws Exception {
397419
try (PathProcessor.TempFile tempFile = pathProcessor.newTempFile()) {
398420
utilPut(task, Files.newOutputStream(tempFile.getPath()), true);
399421
request.PUT(HttpRequest.BodyPublishers.ofFile(tempFile.getPath()));
400-
422+
prepare(request);
401423
try {
402424
HttpResponse<Void> response = send(request.build(), HttpResponse.BodyHandlers.discarding());
403425
if (response.statusCode() >= MULTIPLE_CHOICES) {
@@ -409,6 +431,24 @@ protected void implPut(PutTask task) throws Exception {
409431
}
410432
}
411433

434+
private void prepare(HttpRequest.Builder requestBuilder) {
435+
if (serverAuthentication != null
436+
&& (preemptiveAuth
437+
|| (preemptivePutAuth && requestBuilder.build().method().equals("PUT")))) {
438+
// https://stackoverflow.com/a/58612586
439+
requestBuilder.setHeader(
440+
"Authorization",
441+
getBasicAuthValue(serverAuthentication.getUserName(), serverAuthentication.getPassword()));
442+
}
443+
}
444+
445+
static String getBasicAuthValue(String username, char[] password) {
446+
StringBuilder sb = new StringBuilder(128);
447+
sb.append(username).append(':').append(password);
448+
// Java's HTTP client uses ISO-8859-1 for Basic auth encoding
449+
return "Basic " + Base64.getEncoder().encodeToString(sb.toString().getBytes(ISO_8859_1));
450+
}
451+
412452
private <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler)
413453
throws Exception {
414454
maxConcurrentRequests.acquire();
@@ -437,10 +477,8 @@ private HttpClient createClient(RepositorySystemSession session, RemoteRepositor
437477

438478
String username = repoAuthContext.get(AuthenticationContext.USERNAME);
439479
String password = repoAuthContext.get(AuthenticationContext.PASSWORD);
440-
441-
authentications.put(
442-
Authenticator.RequestorType.SERVER,
443-
new PasswordAuthentication(username, password.toCharArray()));
480+
serverAuthentication = new PasswordAuthentication(username, password.toCharArray());
481+
authentications.put(Authenticator.RequestorType.SERVER, serverAuthentication);
444482
}
445483
}
446484

@@ -519,7 +557,6 @@ public X509Certificate[] getAcceptedIssuers() {
519557
}
520558
}
521559
}
522-
523560
if (!authentications.isEmpty()) {
524561
builder.authenticator(new Authenticator() {
525562
@Override
@@ -528,7 +565,6 @@ protected PasswordAuthentication getPasswordAuthentication() {
528565
}
529566
});
530567
}
531-
532568
return builder.build();
533569
}
534570

maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk-11/src/test/java/org/eclipse/aether/transport/jdk/JdkTransporterTest.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
import org.eclipse.aether.spi.connector.transport.Transporter;
3030
import org.junit.jupiter.api.Disabled;
3131
import org.junit.jupiter.api.Test;
32+
import org.junit.jupiter.api.condition.DisabledOnJre;
33+
import org.junit.jupiter.api.condition.JRE;
3234

35+
import static org.junit.jupiter.api.Assertions.assertEquals;
3336
import static org.junit.jupiter.api.Assertions.assertTrue;
3437
import static org.junit.jupiter.api.Assertions.fail;
3538

@@ -50,24 +53,36 @@ protected void testAuthSchemeReuse() {}
5053
protected void testPut_ProxyUnauthenticated() {}
5154

5255
@Override
53-
@Disabled
56+
@DisabledOnJre(
57+
value = {JRE.JAVA_17, JRE.JAVA_21},
58+
disabledReason = "JDK-8326949")
5459
@Test
55-
protected void testAuthSchemePreemptive() {}
60+
protected void testAuthSchemePreemptive() throws Exception {
61+
super.testAuthSchemePreemptive();
62+
}
5663

5764
@Override
58-
@Disabled
65+
@DisabledOnJre(
66+
value = {JRE.JAVA_17, JRE.JAVA_21},
67+
disabledReason = "JDK-8326949")
5968
@Test
60-
protected void testPut_AuthCache_Preemptive() {}
69+
protected void testPut_AuthCache_Preemptive() throws Exception {
70+
super.testPut_AuthCache_Preemptive();
71+
}
6172

6273
@Override
6374
@Disabled
6475
@Test
6576
protected void testPut_Unauthenticated() {}
6677

6778
@Override
68-
@Disabled
79+
@DisabledOnJre(
80+
value = {JRE.JAVA_17, JRE.JAVA_21},
81+
disabledReason = "JDK-8326949")
6982
@Test
70-
protected void testPut_PreemptiveIsDefault() {}
83+
protected void testPut_PreemptiveIsDefault() throws Exception {
84+
super.testPut_PreemptiveIsDefault();
85+
}
7186

7287
@Override
7388
@Disabled
@@ -113,4 +128,11 @@ void enhanceConnectExceptionMessages() {
113128
fail("We expect ConnectException");
114129
}
115130
}
131+
132+
@Test
133+
void testGetBasicAuthValue() {
134+
assertEquals(
135+
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
136+
JdkTransporter.getBasicAuthValue("Aladdin", "open sesame".toCharArray()));
137+
}
116138
}

0 commit comments

Comments
 (0)