From 48f658f02ec94802472dc65c153ffb1ab38789bb Mon Sep 17 00:00:00 2001 From: Mohammed Hassan Date: Mon, 2 Nov 2026 19:09:36 +0530 Subject: [PATCH] do not hand system proxy credentials to the origin server SystemDefaultCredentialsProvider asked java.net.Authenticator for RequestorType.PROXY credentials whenever no SERVER credentials were found, whatever host issued the challenge. With an Authenticator that answers proxy requests, as is common for corporate proxies, any origin server replying 401 Basic was sent the proxy credentials. Skip the PROXY lookup when the auth scope is the target of the request being executed; 407 challenges from the proxy resolve as before. --- .../SystemDefaultCredentialsProvider.java | 21 +++++++-- .../TestSystemDefaultCredentialsProvider.java | 45 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java index 4812e11a12..b43a30ccbd 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/SystemDefaultCredentialsProvider.java @@ -37,12 +37,14 @@ import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.CredentialsStore; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.util.Args; /** @@ -95,6 +97,16 @@ private static PasswordAuthentication getSystemCreds( requestorType); } + private static boolean isRequestTarget(final AuthScope authScope, final HttpClientContext context) { + final HttpRequest request = context != null ? context.getRequest() : null; + final URIAuthority authority = request != null ? request.getAuthority() : null; + if (authority == null) { + return false; + } + return authority.getHostName().equalsIgnoreCase(authScope.getHost()) + && DefaultSchemePortResolver.INSTANCE.resolve(request.getScheme(), authority) == authScope.getPort(); + } + @Override public Credentials getCredentials(final AuthScope authScope, final HttpContext context) { Args.notNull(authScope, "Auth scope"); @@ -110,9 +122,12 @@ public Credentials getCredentials(final AuthScope authScope, final HttpContext c if (serverCreds != null) { return new UsernamePasswordCredentials(serverCreds.getUserName(), serverCreds.getPassword()); } - final PasswordAuthentication proxyCreds = getSystemCreds(protocol, authScope, Authenticator.RequestorType.PROXY, clientContext); - if (proxyCreds != null) { - return new UsernamePasswordCredentials(proxyCreds.getUserName(), proxyCreds.getPassword()); + // Proxy credentials must never be handed to the origin server the request is addressed to + if (!isRequestTarget(authScope, clientContext)) { + final PasswordAuthentication proxyCreds = getSystemCreds(protocol, authScope, Authenticator.RequestorType.PROXY, clientContext); + if (proxyCreds != null) { + return new UsernamePasswordCredentials(proxyCreds.getUserName(), proxyCreds.getPassword()); + } } } return null; diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java index ce71b055bd..daf8d6d1f6 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestSystemDefaultCredentialsProvider.java @@ -129,6 +129,51 @@ void testSystemCredentialsProviderNoContext() { Assertions.assertEquals(AUTH1.getUserName(), receivedCredentials.getUserPrincipal().getName()); } + @Test + void testSystemCredentialsProviderNoProxyCredentialsForTarget() throws Exception { + + final AuthenticatorDelegate authenticatorDelegate = installProxyAuthenticator(AUTH1); + + final AuthScope authScope = new AuthScope(TARGET_SCHEME1, TARGET_HOST1, TARGET_PORT1, PROMPT1, StandardAuthScheme.BASIC); + final HttpClientContext context = HttpClientContext.create(); + context.setRequest(new HttpGet(TARGET_SCHEME1 + "://" + TARGET_HOST1 + ":" + TARGET_PORT1 + "/")); + + final Credentials receivedCredentials = + new SystemDefaultCredentialsProvider().getCredentials(authScope, context); + + Mockito.verify(authenticatorDelegate, Mockito.never()).getPasswordAuthentication(ArgumentMatchers.any(), + ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.any(), ArgumentMatchers.any(), + ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.eq(RequestorType.PROXY)); + Assertions.assertNull(receivedCredentials); + } + + @Test + void testSystemCredentialsProviderProxyCredentialsForProxy() throws Exception { + + installProxyAuthenticator(AUTH1); + + final AuthScope authScope = new AuthScope(PROXY_PROTOCOL1, PROXY_HOST1, PROXY_PORT1, PROMPT1, StandardAuthScheme.BASIC); + final HttpClientContext context = HttpClientContext.create(); + context.setRequest(new HttpGet(TARGET_SCHEME1 + "://" + TARGET_HOST1 + ":" + TARGET_PORT1 + "/")); + + final Credentials receivedCredentials = + new SystemDefaultCredentialsProvider().getCredentials(authScope, context); + + Assertions.assertNotNull(receivedCredentials); + Assertions.assertEquals(AUTH1.getUserName(), receivedCredentials.getUserPrincipal().getName()); + } + + private AuthenticatorDelegate installProxyAuthenticator(final PasswordAuthentication proxyAuthentication) { + final AuthenticatorDelegate authenticatorDelegate = Mockito.mock(AuthenticatorDelegate.class); + Mockito.when(authenticatorDelegate.getPasswordAuthentication(ArgumentMatchers.any(), + ArgumentMatchers.any(), ArgumentMatchers.anyInt(), + ArgumentMatchers.any(), ArgumentMatchers.any(), + ArgumentMatchers.any(), ArgumentMatchers.any(), + ArgumentMatchers.eq(RequestorType.PROXY))).thenReturn(proxyAuthentication); + Authenticator.setDefault(new DelegatedAuthenticator(authenticatorDelegate)); + return authenticatorDelegate; + } + private AuthenticatorDelegate installAuthenticator(final PasswordAuthentication returedAuthentication) { final AuthenticatorDelegate authenticatorDelegate = Mockito.mock(AuthenticatorDelegate.class); Mockito.when(authenticatorDelegate.getPasswordAuthentication(ArgumentMatchers.anyString(),