Skip to content

Commit 3e41f5e

Browse files
committed
Cache the encoded credentials in BasicAuthenticationInterceptor
Prior to this commit, the Basic Authentication credentials were encoded for each request. This commit addresses this minor performance issue by caching the encoded credentials in BasicAuthenticationInterceptor. In addition, this commit introduces new encodeBasicAuth() and setBasicAuth(String encodedCredentials) methods in HttpHeaders to support this feature. Closes gh-23204
1 parent 5008423 commit 3e41f5e

File tree

2 files changed

+69
-28
lines changed

2 files changed

+69
-28
lines changed

spring-web/src/main/java/org/springframework/http/HttpHeaders.java

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,9 @@ public Set<HttpMethod> getAllow() {
742742
* @throws IllegalArgumentException if either {@code user} or
743743
* {@code password} contain characters that cannot be encoded to ISO-8859-1
744744
* @since 5.1
745+
* @see #setBasicAuth(String)
745746
* @see #setBasicAuth(String, String, Charset)
747+
* @see #encodeBasicAuth(String, String, Charset)
746748
* @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
747749
*/
748750
public void setBasicAuth(String username, String password) {
@@ -759,24 +761,33 @@ public void setBasicAuth(String username, String password) {
759761
* @throws IllegalArgumentException if {@code username} or {@code password}
760762
* contains characters that cannot be encoded to the given charset
761763
* @since 5.1
764+
* @see #setBasicAuth(String)
765+
* @see #setBasicAuth(String, String)
766+
* @see #encodeBasicAuth(String, String, Charset)
762767
* @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
763768
*/
764769
public void setBasicAuth(String username, String password, @Nullable Charset charset) {
765-
Assert.notNull(username, "Username must not be null");
766-
Assert.notNull(password, "Password must not be null");
767-
if (charset == null) {
768-
charset = StandardCharsets.ISO_8859_1;
769-
}
770-
771-
CharsetEncoder encoder = charset.newEncoder();
772-
if (!encoder.canEncode(username) || !encoder.canEncode(password)) {
773-
throw new IllegalArgumentException(
774-
"Username or password contains characters that cannot be encoded to " + charset.displayName());
775-
}
770+
setBasicAuth(encodeBasicAuth(username, password, charset));
771+
}
776772

777-
String credentialsString = username + ":" + password;
778-
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset));
779-
String encodedCredentials = new String(encodedBytes, charset);
773+
/**
774+
* Set the value of the {@linkplain #AUTHORIZATION Authorization} header to
775+
* Basic Authentication based on the given {@linkplain #encodeBasicAuth
776+
* encoded credentials}.
777+
* <p>Favor this method over {@link #setBasicAuth(String, String)} and
778+
* {@link #setBasicAuth(String, String, Charset)} if you wish to cache the
779+
* encoded credentials.
780+
* @param encodedCredentials the encoded credentials
781+
* @throws IllegalArgumentException if supplied credentials string is
782+
* {@code null} or blank
783+
* @since 5.2
784+
* @see #setBasicAuth(String, String)
785+
* @see #setBasicAuth(String, String, Charset)
786+
* @see #encodeBasicAuth(String, String, Charset)
787+
* @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
788+
*/
789+
public void setBasicAuth(String encodedCredentials) {
790+
Assert.hasText(encodedCredentials, "'encodedCredentials' must not be null or blank");
780791
set(AUTHORIZATION, "Basic " + encodedCredentials);
781792
}
782793

@@ -1781,6 +1792,41 @@ public static String formatHeaders(MultiValueMap<String, String> headers) {
17811792
.collect(Collectors.joining(", ", "[", "]"));
17821793
}
17831794

1795+
/**
1796+
* Encode the given username and password into Basic Authentication credentials.
1797+
* <p>The encoded credentials returned by this method can be supplied to
1798+
* {@link #setBasicAuth(String)} to set the Basic Authentication header.
1799+
* @param username the username
1800+
* @param password the password
1801+
* @param charset the charset to use to convert the credentials into an octet
1802+
* sequence. Defaults to {@linkplain StandardCharsets#ISO_8859_1 ISO-8859-1}.
1803+
* @throws IllegalArgumentException if {@code username} or {@code password}
1804+
* contains characters that cannot be encoded to the given charset
1805+
* @since 5.2
1806+
* @see #setBasicAuth(String)
1807+
* @see #setBasicAuth(String, String)
1808+
* @see #setBasicAuth(String, String, Charset)
1809+
* @see <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
1810+
*/
1811+
public static String encodeBasicAuth(String username, String password, @Nullable Charset charset) {
1812+
Assert.notNull(username, "Username must not be null");
1813+
Assert.doesNotContain(username, ":", "Username must not contain a colon");
1814+
Assert.notNull(password, "Password must not be null");
1815+
if (charset == null) {
1816+
charset = StandardCharsets.ISO_8859_1;
1817+
}
1818+
1819+
CharsetEncoder encoder = charset.newEncoder();
1820+
if (!encoder.canEncode(username) || !encoder.canEncode(password)) {
1821+
throw new IllegalArgumentException(
1822+
"Username or password contains characters that cannot be encoded to " + charset.displayName());
1823+
}
1824+
1825+
String credentialsString = username + ":" + password;
1826+
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset));
1827+
return new String(encodedBytes, charset);
1828+
}
1829+
17841830
// Package-private: used in ResponseCookie
17851831
static String formatDate(long date) {
17861832
Instant instant = Instant.ofEpochMilli(date);

spring-web/src/main/java/org/springframework/http/client/support/BasicAuthenticationInterceptor.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,25 +25,21 @@
2525
import org.springframework.http.client.ClientHttpRequestInterceptor;
2626
import org.springframework.http.client.ClientHttpResponse;
2727
import org.springframework.lang.Nullable;
28-
import org.springframework.util.Assert;
2928

3029
/**
3130
* {@link ClientHttpRequestInterceptor} to apply a given HTTP Basic Authentication
32-
* username/password pair, unless a custom Authorization header has been set before.
31+
* username/password pair, unless a custom {@code Authorization} header has
32+
* already been set.
3333
*
3434
* @author Juergen Hoeller
35+
* @author Sam Brannen
3536
* @since 5.1.1
3637
* @see HttpHeaders#setBasicAuth
3738
* @see HttpHeaders#AUTHORIZATION
3839
*/
3940
public class BasicAuthenticationInterceptor implements ClientHttpRequestInterceptor {
4041

41-
private final String username;
42-
43-
private final String password;
44-
45-
@Nullable
46-
private final Charset charset;
42+
private final String encodedCredentials;
4743

4844

4945
/**
@@ -52,6 +48,7 @@ public class BasicAuthenticationInterceptor implements ClientHttpRequestIntercep
5248
* @param username the username to use
5349
* @param password the password to use
5450
* @see HttpHeaders#setBasicAuth(String, String)
51+
* @see HttpHeaders#encodeBasicAuth(String, String, Charset)
5552
*/
5653
public BasicAuthenticationInterceptor(String username, String password) {
5754
this(username, password, null);
@@ -64,12 +61,10 @@ public BasicAuthenticationInterceptor(String username, String password) {
6461
* @param password the password to use
6562
* @param charset the charset to use
6663
* @see HttpHeaders#setBasicAuth(String, String, Charset)
64+
* @see HttpHeaders#encodeBasicAuth(String, String, Charset)
6765
*/
6866
public BasicAuthenticationInterceptor(String username, String password, @Nullable Charset charset) {
69-
Assert.doesNotContain(username, ":", "Username must not contain a colon");
70-
this.username = username;
71-
this.password = password;
72-
this.charset = charset;
67+
this.encodedCredentials = HttpHeaders.encodeBasicAuth(username, password, charset);
7368
}
7469

7570

@@ -79,7 +74,7 @@ public ClientHttpResponse intercept(
7974

8075
HttpHeaders headers = request.getHeaders();
8176
if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
82-
headers.setBasicAuth(this.username, this.password, this.charset);
77+
headers.setBasicAuth(this.encodedCredentials);
8378
}
8479
return execution.execute(request, body);
8580
}

0 commit comments

Comments
 (0)