Skip to content

Commit c18207f

Browse files
authored
Fix ApacheHttpClient Proxy Preemptive Auth Failure (#6333)
* Fix proxy preemotive authentication failure * Add changelog * Fixing checkstyle error * Addressing PR feedback * Addressing PR feedback * Adding test for not-preemptive auth codepath * Updating test name
1 parent 038534c commit c18207f

File tree

3 files changed

+164
-2
lines changed

3 files changed

+164
-2
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Fixed 407 Proxy Authentication error when preemptiveBasicAuthenticationEnabled is true. Fixes [#5884](https://github.com/aws/aws-sdk-java-v2/issues/5884)."
6+
}

http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/utils/ApacheUtils.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.UncheckedIOException;
2020
import org.apache.http.HttpEntity;
2121
import org.apache.http.HttpHost;
22+
import org.apache.http.auth.AUTH;
2223
import org.apache.http.auth.AuthScope;
2324
import org.apache.http.auth.Credentials;
2425
import org.apache.http.auth.NTCredentials;
@@ -30,6 +31,7 @@
3031
import org.apache.http.impl.auth.BasicScheme;
3132
import org.apache.http.impl.client.BasicAuthCache;
3233
import org.apache.http.impl.client.BasicCredentialsProvider;
34+
import org.apache.http.message.BasicHeader;
3335
import software.amazon.awssdk.annotations.SdkInternalApi;
3436
import software.amazon.awssdk.http.apache.ProxyConfiguration;
3537
import software.amazon.awssdk.utils.Logger;
@@ -149,10 +151,16 @@ private static void addPreemptiveAuthenticationProxy(HttpClientContext clientCon
149151
AuthCache authCache = new BasicAuthCache();
150152
// Generate BASIC scheme object and add it to the local auth cache
151153
BasicScheme basicAuth = new BasicScheme();
152-
authCache.put(targetHost, basicAuth);
154+
try {
155+
basicAuth.processChallenge(new BasicHeader(AUTH.PROXY_AUTH, "BASIC realm=default"));
156+
authCache.put(targetHost, basicAuth);
157+
clientContext.setAuthCache(authCache);
158+
} catch (Exception e) {
159+
logger.debug(() -> "Failed to process synthetic challenge for preemptive proxy authentication: " +
160+
e.getMessage());
161+
}
153162

154163
clientContext.setCredentialsProvider(credsProvider);
155-
clientContext.setAuthCache(authCache);
156164
}
157165
}
158166

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright Amazon.com, 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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.apache;
17+
18+
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
19+
import static com.github.tomakehurst.wiremock.client.WireMock.any;
20+
import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;
21+
import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
22+
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
import com.github.tomakehurst.wiremock.WireMockServer;
26+
import com.github.tomakehurst.wiremock.client.WireMock;
27+
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
28+
import java.net.URI;
29+
import org.junit.jupiter.api.AfterEach;
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
import software.amazon.awssdk.http.HttpExecuteRequest;
33+
import software.amazon.awssdk.http.HttpExecuteResponse;
34+
import software.amazon.awssdk.http.SdkHttpClient;
35+
import software.amazon.awssdk.http.SdkHttpMethod;
36+
import software.amazon.awssdk.http.SdkHttpRequest;
37+
38+
/**
39+
* Tests proxy preemptive authentication functionality.
40+
*
41+
* Verifies that when preemptiveBasicAuthenticationEnabled(true) is configured,
42+
* the Proxy-Authorization header is sent with the first request to the proxy.
43+
*/
44+
public class ApacheHttpClientProxyAuthTest {
45+
46+
private WireMockServer mockProxy;
47+
private SdkHttpClient httpClient;
48+
49+
@BeforeEach
50+
public void setup() {
51+
mockProxy = new WireMockServer(WireMockConfiguration.options().dynamicPort());
52+
mockProxy.start();
53+
}
54+
55+
@AfterEach
56+
public void teardown() {
57+
if (httpClient != null) {
58+
httpClient.close();
59+
}
60+
if (mockProxy != null) {
61+
mockProxy.stop();
62+
}
63+
}
64+
65+
@Test
66+
public void proxyAuthentication_whenPreemptiveAuthEnabled_shouldSendProxyAuthorizationHeader() throws Exception {
67+
mockProxy.stubFor(any(anyUrl())
68+
.withHeader("Proxy-Authorization", matching("Basic .+"))
69+
.willReturn(aResponse()
70+
.withStatus(200)
71+
.withBody("Success")));
72+
73+
// Create HTTP client with preemptive proxy authentication enabled
74+
httpClient = ApacheHttpClient.builder()
75+
.proxyConfiguration(ProxyConfiguration.builder()
76+
.endpoint(URI.create("http://localhost:" + mockProxy.port()))
77+
.username("testuser")
78+
.password("testpass")
79+
.preemptiveBasicAuthenticationEnabled(true)
80+
.build())
81+
.build();
82+
83+
// Create a request
84+
SdkHttpRequest request = SdkHttpRequest.builder()
85+
.method(SdkHttpMethod.GET)
86+
.uri(URI.create("http://example.com/test"))
87+
.build();
88+
89+
HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
90+
.request(request)
91+
.build();
92+
93+
// Execute the request - should succeed with preemptive auth header
94+
HttpExecuteResponse response = httpClient.prepareRequest(executeRequest).call();
95+
assertThat(response.httpResponse().statusCode()).isEqualTo(200);
96+
97+
mockProxy.verify(1, anyRequestedFor(anyUrl()));
98+
mockProxy.verify(WireMock.getRequestedFor(anyUrl())
99+
.withHeader("Proxy-Authorization", matching("Basic .+")));
100+
}
101+
102+
@Test
103+
public void proxyAuthentication_whenPreemptiveAuthDisabled_shouldUseChallengeResponseAuth() throws Exception {
104+
// First request without auth header should get 407
105+
mockProxy.stubFor(any(anyUrl())
106+
.willReturn(aResponse()
107+
.withStatus(407)
108+
.withHeader("Proxy-Authenticate", "Basic realm=\"proxy\"")));
109+
110+
// Second request with auth header should succeed
111+
mockProxy.stubFor(any(anyUrl())
112+
.withHeader("Proxy-Authorization", matching("Basic .+"))
113+
.willReturn(aResponse()
114+
.withStatus(200)
115+
.withBody("Success")));
116+
117+
// Create HTTP client with preemptive proxy authentication disabled
118+
httpClient = ApacheHttpClient.builder()
119+
.proxyConfiguration(ProxyConfiguration.builder()
120+
.endpoint(URI.create("http://localhost:" + mockProxy.port()))
121+
.username("testuser")
122+
.password("testpass")
123+
.preemptiveBasicAuthenticationEnabled(false)
124+
.build())
125+
.build();
126+
127+
// Create a request
128+
SdkHttpRequest request = SdkHttpRequest.builder()
129+
.method(SdkHttpMethod.GET)
130+
.uri(URI.create("http://example.com/test"))
131+
.build();
132+
133+
HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
134+
.request(request)
135+
.build();
136+
137+
// Execute the request - should succeed after challenge-response
138+
HttpExecuteResponse response = httpClient.prepareRequest(executeRequest).call();
139+
assertThat(response.httpResponse().statusCode()).isEqualTo(200);
140+
141+
// Verify challenge-response flow - 2 requests total
142+
mockProxy.verify(2, anyRequestedFor(anyUrl()));
143+
// First request without auth header
144+
mockProxy.verify(1, anyRequestedFor(anyUrl()).withoutHeader("Proxy-Authorization"));
145+
// Second request with auth header
146+
mockProxy.verify(1, anyRequestedFor(anyUrl()).withHeader("Proxy-Authorization", matching("Basic .+")));
147+
}
148+
}

0 commit comments

Comments
 (0)