diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java index 8475f1da87..5506c0222b 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultUserTokenHandler.java @@ -31,10 +31,12 @@ import javax.net.ssl.SSLSession; import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.UserTokenHandler; import org.apache.hc.client5.http.auth.AuthExchange; import org.apache.hc.client5.http.auth.AuthScheme; import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.routing.RoutingSupport; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.http.HttpHost; @@ -64,6 +66,28 @@ public class DefaultUserTokenHandler implements UserTokenHandler { */ public static final DefaultUserTokenHandler INSTANCE = new DefaultUserTokenHandler(); + private final SchemePortResolver schemePortResolver; + + /** + * Creates a new {@code DefaultUserTokenHandler} using the given {@link SchemePortResolver}. + * If {@code schemePortResolver} is {@code null}, this will default to + * {@link DefaultSchemePortResolver#INSTANCE}. + * + * @param schemePortResolver the resolver to use for determining default ports + * @since 5.7 + */ + public DefaultUserTokenHandler(final SchemePortResolver schemePortResolver) { + this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE; + } + + /** + * Creates a new {@code DefaultUserTokenHandler} with the default + * {@link DefaultSchemePortResolver#INSTANCE}. + */ + public DefaultUserTokenHandler() { + this(null); + } + @Override public Object getUserToken(final HttpRoute route, final HttpContext context) { return getUserToken(route, null, context); @@ -74,7 +98,10 @@ public Object getUserToken(final HttpRoute route, final HttpRequest request, fin final HttpClientContext clientContext = HttpClientContext.cast(context); - final HttpHost target = request != null ? new HttpHost(request.getScheme(), request.getAuthority()) : route.getTargetHost(); + // Auth exchanges are keyed by the target with its port resolved (see ProtocolExec) + final HttpHost target = request != null + ? RoutingSupport.normalize(new HttpHost(request.getScheme(), request.getAuthority()), schemePortResolver) + : route.getTargetHost(); final AuthExchange targetAuthExchange = clientContext.getAuthExchange(target); if (targetAuthExchange != null) { diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java index 82b38fa864..6c1b8bb492 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java @@ -1028,7 +1028,7 @@ public CloseableHttpAsyncClient build() { UserTokenHandler userTokenHandlerCopy = this.userTokenHandler; if (userTokenHandlerCopy == null) { if (!connectionStateDisabled) { - userTokenHandlerCopy = DefaultUserTokenHandler.INSTANCE; + userTokenHandlerCopy = schemePortResolver != null ? new DefaultUserTokenHandler(schemePortResolver) : DefaultUserTokenHandler.INSTANCE; } else { userTokenHandlerCopy = NoopUserTokenHandler.INSTANCE; } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java index d2d70ed7d4..52eac1641f 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java @@ -887,7 +887,7 @@ public CloseableHttpClient build() { UserTokenHandler userTokenHandlerCopy = this.userTokenHandler; if (userTokenHandlerCopy == null) { if (!connectionStateDisabled) { - userTokenHandlerCopy = DefaultUserTokenHandler.INSTANCE; + userTokenHandlerCopy = schemePortResolver != null ? new DefaultUserTokenHandler(schemePortResolver) : DefaultUserTokenHandler.INSTANCE; } else { userTokenHandlerCopy = NoopUserTokenHandler.INSTANCE; } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultUserTokenHandler.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultUserTokenHandler.java new file mode 100644 index 0000000000..79be2b827b --- /dev/null +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/TestDefaultUserTokenHandler.java @@ -0,0 +1,83 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.impl; + +import java.security.Principal; + +import org.apache.hc.client5.http.HttpRoute; +import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.auth.AuthExchange; +import org.apache.hc.client5.http.auth.AuthScheme; +import org.apache.hc.client5.http.auth.BasicUserPrincipal; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.HttpHost; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class TestDefaultUserTokenHandler { + + private static AuthScheme connectionBasedScheme(final Principal principal) { + final AuthScheme authScheme = Mockito.mock(AuthScheme.class); + Mockito.when(authScheme.isConnectionBased()).thenReturn(true); + Mockito.when(authScheme.getPrincipal()).thenReturn(principal); + return authScheme; + } + + @Test + void testConnectionBasedPrincipalRequestWithoutPort() { + final Principal principal = new BasicUserPrincipal("alice"); + final HttpClientContext context = HttpClientContext.create(); + // ProtocolExec keys the target auth exchange with the default port resolved + final AuthExchange authExchange = context.getAuthExchange(new HttpHost("https", "somehost", 443)); + authExchange.select(connectionBasedScheme(principal)); + authExchange.setState(AuthExchange.State.SUCCESS); + + final HttpRoute route = new HttpRoute(new HttpHost("https", "somehost", 443)); + final HttpGet request = new HttpGet("https://somehost/stuff"); + + Assertions.assertEquals(principal, DefaultUserTokenHandler.INSTANCE.getUserToken(route, request, context)); + } + + @Test + void testConnectionBasedPrincipalCustomSchemePortResolver() { + final SchemePortResolver schemePortResolver = host -> host.getPort() > 0 ? host.getPort() : 8443; + final Principal principal = new BasicUserPrincipal("alice"); + final HttpClientContext context = HttpClientContext.create(); + final AuthExchange authExchange = context.getAuthExchange(new HttpHost("https", "somehost", 8443)); + authExchange.select(connectionBasedScheme(principal)); + authExchange.setState(AuthExchange.State.SUCCESS); + + final HttpRoute route = new HttpRoute(new HttpHost("https", "somehost", 8443)); + final HttpGet request = new HttpGet("https://somehost/stuff"); + + final DefaultUserTokenHandler userTokenHandler = new DefaultUserTokenHandler(schemePortResolver); + Assertions.assertEquals(principal, userTokenHandler.getUserToken(route, request, context)); + } + +}