From e6996650ab259d61d7931e2f27daba3a79def2d2 Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Tue, 12 Aug 2025 09:46:19 -0700 Subject: [PATCH 1/7] Fix proxy preemotive authentication failure --- .../apache/internal/utils/ApacheUtils.java | 7 ++ .../http/apache/ProxyPreemptiveAuthTest.java | 100 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java diff --git a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java index e35eca470d53..e461f7cef80a 100644 --- a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java +++ b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java @@ -30,6 +30,8 @@ import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.auth.AUTH; +import org.apache.http.message.BasicHeader; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.http.apache.ProxyConfiguration; import software.amazon.awssdk.utils.Logger; @@ -149,6 +151,11 @@ private static void addPreemptiveAuthenticationProxy(HttpClientContext clientCon AuthCache authCache = new BasicAuthCache(); // Generate BASIC scheme object and add it to the local auth cache BasicScheme basicAuth = new BasicScheme(); + try { + basicAuth.processChallenge(new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=default")); + } catch (Exception e) { + logger.warn(() -> "Failed to process synthetic challenge for preemptive proxy authentication: " + e.getMessage()); + } authCache.put(targetHost, basicAuth); clientContext.setCredentialsProvider(credsProvider); diff --git a/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java new file mode 100644 index 000000000000..31fe84df50d6 --- /dev/null +++ b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.http.apache; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import java.net.URI; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.http.HttpExecuteRequest; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.http.SdkHttpRequest; + +/** + * Tests proxy preemptive authentication functionality. + * + * Verifies that when preemptiveBasicAuthenticationEnabled(true) is configured, + * the Proxy-Authorization header is sent with the first request to the proxy. + */ +public class ProxyPreemptiveAuthTest { + + private WireMockServer proxyServer; + private SdkHttpClient httpClient; + + @BeforeEach + public void setup() { + proxyServer = new WireMockServer(WireMockConfiguration.options().dynamicPort()); + proxyServer.start(); + } + + @AfterEach + public void teardown() { + if (httpClient != null) { + httpClient.close(); + } + if (proxyServer != null) { + proxyServer.stop(); + } + } + + @Test + public void testPreemptiveAuthenticationSendsProxyAuthorizationHeader() throws Exception { + proxyServer.stubFor(any(anyUrl()) + .withHeader("Proxy-Authorization", matching("Basic .+")) + .willReturn(aResponse() + .withStatus(200) + .withBody("Success"))); + + // Create HTTP client with preemptive proxy authentication enabled + httpClient = ApacheHttpClient.builder() + .proxyConfiguration(ProxyConfiguration.builder() + .endpoint(URI.create("http://localhost:" + proxyServer.port())) + .username("testuser") + .password("testpass") + .preemptiveBasicAuthenticationEnabled(true) + .build()) + .build(); + + // Create a request + SdkHttpRequest request = SdkHttpRequest.builder() + .method(SdkHttpMethod.GET) + .uri(URI.create("http://example.com/test")) + .build(); + + HttpExecuteRequest executeRequest = HttpExecuteRequest.builder() + .request(request) + .build(); + + // Execute the request - should succeed with preemptive auth header + HttpExecuteResponse response = httpClient.prepareRequest(executeRequest).call(); + assertThat(response.httpResponse().statusCode()).isEqualTo(200); + + // Verify that the proxy received the request with Proxy-Authorization header + proxyServer.verify(WireMock.getRequestedFor(anyUrl()) + .withHeader("Proxy-Authorization", matching("Basic .+"))); + } +} From 522eee6639e125a32176142d9b082d4fbe0866fb Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Tue, 12 Aug 2025 09:57:21 -0700 Subject: [PATCH 2/7] Add changelog --- .changes/next-release/bugfix-AWSSDKforJavav2-cefa8ba.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/next-release/bugfix-AWSSDKforJavav2-cefa8ba.json diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-cefa8ba.json b/.changes/next-release/bugfix-AWSSDKforJavav2-cefa8ba.json new file mode 100644 index 000000000000..dd0ee10b36c9 --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-cefa8ba.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Fixed 407 Proxy Authentication error when preemptiveBasicAuthenticationEnabled is true. Fixes [#5884](https://github.com/aws/aws-sdk-java-v2/issues/5884)." +} From 29578a82084ac33737f3b0ff4a4490573f6b6130 Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Tue, 12 Aug 2025 10:18:59 -0700 Subject: [PATCH 3/7] Fixing checkstyle error --- .../amazon/awssdk/http/apache/internal/utils/ApacheUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java index e461f7cef80a..76a622d0b875 100644 --- a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java +++ b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java @@ -19,6 +19,7 @@ import java.io.UncheckedIOException; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; +import org.apache.http.auth.AUTH; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.NTCredentials; @@ -30,7 +31,6 @@ import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.BasicAuthCache; import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.auth.AUTH; import org.apache.http.message.BasicHeader; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.http.apache.ProxyConfiguration; From bf18865ac8edaa716b8d2dc9cf79ea3c51c1fec5 Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Tue, 12 Aug 2025 15:17:46 -0700 Subject: [PATCH 4/7] Addressing PR feedback --- .../amazon/awssdk/http/apache/internal/utils/ApacheUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java index 76a622d0b875..59416746b7f5 100644 --- a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java +++ b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java @@ -22,6 +22,7 @@ import org.apache.http.auth.AUTH; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; +import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.NTCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.CredentialsProvider; @@ -153,10 +154,10 @@ private static void addPreemptiveAuthenticationProxy(HttpClientContext clientCon BasicScheme basicAuth = new BasicScheme(); try { basicAuth.processChallenge(new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=default")); + authCache.put(targetHost, basicAuth); } catch (Exception e) { logger.warn(() -> "Failed to process synthetic challenge for preemptive proxy authentication: " + e.getMessage()); } - authCache.put(targetHost, basicAuth); clientContext.setCredentialsProvider(credsProvider); clientContext.setAuthCache(authCache); From b66d7d95b98711367a6e941f32e3a9ecb89e5ff5 Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Tue, 12 Aug 2025 15:33:41 -0700 Subject: [PATCH 5/7] Addressing PR feedback --- .../amazon/awssdk/http/apache/internal/utils/ApacheUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java index 59416746b7f5..3b0a60df6108 100644 --- a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java +++ b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java @@ -155,12 +155,12 @@ private static void addPreemptiveAuthenticationProxy(HttpClientContext clientCon try { basicAuth.processChallenge(new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=default")); authCache.put(targetHost, basicAuth); + clientContext.setAuthCache(authCache); } catch (Exception e) { logger.warn(() -> "Failed to process synthetic challenge for preemptive proxy authentication: " + e.getMessage()); } clientContext.setCredentialsProvider(credsProvider); - clientContext.setAuthCache(authCache); } } From 25614d6b348515c2e5246cc214fb49a9cc9dcb6c Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Wed, 13 Aug 2025 10:17:08 -0700 Subject: [PATCH 6/7] Adding test for not-preemptive auth codepath --- .../apache/internal/utils/ApacheUtils.java | 3 +- .../http/apache/ProxyPreemptiveAuthTest.java | 70 ++++++++++++++++--- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java index 3b0a60df6108..e4617416f298 100644 --- a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java +++ b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java @@ -22,7 +22,6 @@ import org.apache.http.auth.AUTH; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; -import org.apache.http.auth.MalformedChallengeException; import org.apache.http.auth.NTCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.CredentialsProvider; @@ -157,7 +156,7 @@ private static void addPreemptiveAuthenticationProxy(HttpClientContext clientCon authCache.put(targetHost, basicAuth); clientContext.setAuthCache(authCache); } catch (Exception e) { - logger.warn(() -> "Failed to process synthetic challenge for preemptive proxy authentication: " + e.getMessage()); + logger.debug(() -> "Failed to process synthetic challenge for preemptive proxy authentication: " + e.getMessage()); } clientContext.setCredentialsProvider(credsProvider); diff --git a/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java index 31fe84df50d6..9e64b89b4180 100644 --- a/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java +++ b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java @@ -17,6 +17,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; import static com.github.tomakehurst.wiremock.client.WireMock.matching; import static org.assertj.core.api.Assertions.assertThat; @@ -42,13 +43,13 @@ */ public class ProxyPreemptiveAuthTest { - private WireMockServer proxyServer; + private WireMockServer mockProxy; private SdkHttpClient httpClient; @BeforeEach public void setup() { - proxyServer = new WireMockServer(WireMockConfiguration.options().dynamicPort()); - proxyServer.start(); + mockProxy = new WireMockServer(WireMockConfiguration.options().dynamicPort()); + mockProxy.start(); } @AfterEach @@ -56,14 +57,14 @@ public void teardown() { if (httpClient != null) { httpClient.close(); } - if (proxyServer != null) { - proxyServer.stop(); + if (mockProxy != null) { + mockProxy.stop(); } } @Test - public void testPreemptiveAuthenticationSendsProxyAuthorizationHeader() throws Exception { - proxyServer.stubFor(any(anyUrl()) + public void addPreemptiveAuthenticationProxy_whenPreemptiveAuthEnabled_shouldSendProxyAuthorizationHeader() throws Exception { + mockProxy.stubFor(any(anyUrl()) .withHeader("Proxy-Authorization", matching("Basic .+")) .willReturn(aResponse() .withStatus(200) @@ -72,7 +73,7 @@ public void testPreemptiveAuthenticationSendsProxyAuthorizationHeader() throws E // Create HTTP client with preemptive proxy authentication enabled httpClient = ApacheHttpClient.builder() .proxyConfiguration(ProxyConfiguration.builder() - .endpoint(URI.create("http://localhost:" + proxyServer.port())) + .endpoint(URI.create("http://localhost:" + mockProxy.port())) .username("testuser") .password("testpass") .preemptiveBasicAuthenticationEnabled(true) @@ -92,9 +93,56 @@ public void testPreemptiveAuthenticationSendsProxyAuthorizationHeader() throws E // Execute the request - should succeed with preemptive auth header HttpExecuteResponse response = httpClient.prepareRequest(executeRequest).call(); assertThat(response.httpResponse().statusCode()).isEqualTo(200); - - // Verify that the proxy received the request with Proxy-Authorization header - proxyServer.verify(WireMock.getRequestedFor(anyUrl()) + + mockProxy.verify(1, anyRequestedFor(anyUrl())); + mockProxy.verify(WireMock.getRequestedFor(anyUrl()) .withHeader("Proxy-Authorization", matching("Basic .+"))); } + + @Test + public void addPreemptiveAuthenticationProxy_whenPreemptiveAuthDisabled_shouldUseChallengeResponseAuth() throws Exception { + // First request without auth header should get 407 + mockProxy.stubFor(any(anyUrl()) + .willReturn(aResponse() + .withStatus(407) + .withHeader("Proxy-Authenticate", "Basic realm=\"proxy\""))); + + // Second request with auth header should succeed + mockProxy.stubFor(any(anyUrl()) + .withHeader("Proxy-Authorization", matching("Basic .+")) + .willReturn(aResponse() + .withStatus(200) + .withBody("Success"))); + + // Create HTTP client with preemptive proxy authentication disabled + httpClient = ApacheHttpClient.builder() + .proxyConfiguration(ProxyConfiguration.builder() + .endpoint(URI.create("http://localhost:" + mockProxy.port())) + .username("testuser") + .password("testpass") + .preemptiveBasicAuthenticationEnabled(false) + .build()) + .build(); + + // Create a request + SdkHttpRequest request = SdkHttpRequest.builder() + .method(SdkHttpMethod.GET) + .uri(URI.create("http://example.com/test")) + .build(); + + HttpExecuteRequest executeRequest = HttpExecuteRequest.builder() + .request(request) + .build(); + + // Execute the request - should succeed after challenge-response + HttpExecuteResponse response = httpClient.prepareRequest(executeRequest).call(); + assertThat(response.httpResponse().statusCode()).isEqualTo(200); + + // Verify challenge-response flow - 2 requests total + mockProxy.verify(2, anyRequestedFor(anyUrl())); + // First request without auth header + mockProxy.verify(1, anyRequestedFor(anyUrl()).withoutHeader("Proxy-Authorization")); + // Second request with auth header + mockProxy.verify(1, anyRequestedFor(anyUrl()).withHeader("Proxy-Authorization", matching("Basic .+"))); + } } From 5ffe8e146837d1839b2e2ee64e364603cafb9007 Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Wed, 13 Aug 2025 11:20:27 -0700 Subject: [PATCH 7/7] Updating test name --- .../awssdk/http/apache/internal/utils/ApacheUtils.java | 3 ++- ...tiveAuthTest.java => ApacheHttpClientProxyAuthTest.java} | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) rename http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/{ProxyPreemptiveAuthTest.java => ApacheHttpClientProxyAuthTest.java} (95%) diff --git a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java index e4617416f298..dbf194cd73e2 100644 --- a/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java +++ b/http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java @@ -156,7 +156,8 @@ private static void addPreemptiveAuthenticationProxy(HttpClientContext clientCon authCache.put(targetHost, basicAuth); clientContext.setAuthCache(authCache); } catch (Exception e) { - logger.debug(() -> "Failed to process synthetic challenge for preemptive proxy authentication: " + e.getMessage()); + logger.debug(() -> "Failed to process synthetic challenge for preemptive proxy authentication: " + + e.getMessage()); } clientContext.setCredentialsProvider(credsProvider); diff --git a/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientProxyAuthTest.java similarity index 95% rename from http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java rename to http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientProxyAuthTest.java index 9e64b89b4180..2c021cae41fa 100644 --- a/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ProxyPreemptiveAuthTest.java +++ b/http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/ApacheHttpClientProxyAuthTest.java @@ -41,7 +41,7 @@ * Verifies that when preemptiveBasicAuthenticationEnabled(true) is configured, * the Proxy-Authorization header is sent with the first request to the proxy. */ -public class ProxyPreemptiveAuthTest { +public class ApacheHttpClientProxyAuthTest { private WireMockServer mockProxy; private SdkHttpClient httpClient; @@ -63,7 +63,7 @@ public void teardown() { } @Test - public void addPreemptiveAuthenticationProxy_whenPreemptiveAuthEnabled_shouldSendProxyAuthorizationHeader() throws Exception { + public void proxyAuthentication_whenPreemptiveAuthEnabled_shouldSendProxyAuthorizationHeader() throws Exception { mockProxy.stubFor(any(anyUrl()) .withHeader("Proxy-Authorization", matching("Basic .+")) .willReturn(aResponse() @@ -100,7 +100,7 @@ public void addPreemptiveAuthenticationProxy_whenPreemptiveAuthEnabled_shouldSen } @Test - public void addPreemptiveAuthenticationProxy_whenPreemptiveAuthDisabled_shouldUseChallengeResponseAuth() throws Exception { + public void proxyAuthentication_whenPreemptiveAuthDisabled_shouldUseChallengeResponseAuth() throws Exception { // First request without auth header should get 407 mockProxy.stubFor(any(anyUrl()) .willReturn(aResponse()