diff --git a/client/src/main/java/org/asynchttpclient/Realm.java b/client/src/main/java/org/asynchttpclient/Realm.java index cf770dafe..b41203dd1 100644 --- a/client/src/main/java/org/asynchttpclient/Realm.java +++ b/client/src/main/java/org/asynchttpclient/Realm.java @@ -25,6 +25,7 @@ import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.SecureRandom; +import java.util.Base64; import java.util.Map; import static java.nio.charset.StandardCharsets.ISO_8859_1; @@ -70,6 +71,7 @@ public class Realm { private final boolean userhash; private final @Nullable String sid; private final int maxIterationCount; + private @Nullable String basicAuthHeader; private Realm(@Nullable AuthScheme scheme, @Nullable String principal, @@ -245,6 +247,23 @@ public int getMaxIterationCount() { return maxIterationCount; } + /** + * Returns the lazily computed and cached HTTP Basic authorization header for this immutable realm. + * Concurrent first calls may compute the same value more than once. + * + * @return the Basic authorization header + * @since 3.0.12 + */ + public String getBasicAuthHeader() { + String header = basicAuthHeader; + if (header == null) { + String s = principal + ':' + password; + header = "Basic " + Base64.getEncoder().encodeToString(s.getBytes(charset)); + basicAuthHeader = header; + } + return header; + } + @Override public String toString() { return "Realm{" + diff --git a/client/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java b/client/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java index 66d2b73b3..c6dcd34f2 100644 --- a/client/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java +++ b/client/src/main/java/org/asynchttpclient/util/AuthenticatorUtils.java @@ -32,7 +32,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.security.MessageDigest; -import java.util.Base64; import java.util.List; import java.util.Set; @@ -87,12 +86,7 @@ private AuthenticatorUtils() { } private static @Nullable String computeBasicAuthentication(@Nullable Realm realm) { - return realm != null ? computeBasicAuthentication(realm.getPrincipal(), realm.getPassword(), realm.getCharset()) : null; - } - - private static String computeBasicAuthentication(@Nullable String principal, @Nullable String password, Charset charset) { - String s = principal + ':' + password; - return "Basic " + Base64.getEncoder().encodeToString(s.getBytes(charset)); + return realm != null ? realm.getBasicAuthHeader() : null; } public static String computeRealmURI(Uri uri, boolean useAbsoluteURI, boolean omitQuery) { diff --git a/client/src/test/java/org/asynchttpclient/util/AuthenticatorUtilsTest.java b/client/src/test/java/org/asynchttpclient/util/AuthenticatorUtilsTest.java index c98f19483..eea6cd5bc 100644 --- a/client/src/test/java/org/asynchttpclient/util/AuthenticatorUtilsTest.java +++ b/client/src/test/java/org/asynchttpclient/util/AuthenticatorUtilsTest.java @@ -40,12 +40,32 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class AuthenticatorUtilsTest { + @Test + void preemptiveBasicAuthorizationHeaderIsMemoizedOnRealm() { + Request request = new RequestBuilder("GET") + .setUrl("http://example.com/api/users") + .build(); + Realm realm = new Realm.Builder("user", "pass") + .setScheme(Realm.AuthScheme.BASIC) + .setUsePreemptiveAuth(true) + .build(); + + String first = AuthenticatorUtils.perRequestAuthorizationHeader(request, realm); + String second = AuthenticatorUtils.perRequestAuthorizationHeader(request, realm); + String proxy = AuthenticatorUtils.perRequestProxyAuthorizationHeader(request, realm); + + assertEquals("Basic dXNlcjpwYXNz", first); + assertSame(first, second); + assertSame(first, proxy); + } + @Test void computeBodyHashEmptyBody() throws Exception { Request request = new RequestBuilder("GET")