diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctions.java index 4dbae89c9a01..ff6834fb1527 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctions.java @@ -17,9 +17,11 @@ package org.springframework.web.reactive.function.client; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBufferUtils; @@ -34,6 +36,7 @@ * @author Rob Winch * @author Arjen Poutsma * @author Sam Brannen + * @author Kai Zander * @since 5.0 */ public abstract class ExchangeFilterFunctions { @@ -76,6 +79,7 @@ public static ExchangeFilterFunction statusError(Predicate statu * Return a filter that applies HTTP Basic Authentication to the request * headers via {@link HttpHeaders#setBasicAuth(String)} and * {@link HttpHeaders#encodeBasicAuth(String, String, Charset)}. + *

{@linkplain StandardCharsets#ISO_8859_1 ISO-8859-1} is used to convert the credentials into an octet sequence. * @param username the username * @param password the password * @return the filter to add authentication headers with @@ -83,7 +87,24 @@ public static ExchangeFilterFunction statusError(Predicate statu * @see HttpHeaders#setBasicAuth(String) */ public static ExchangeFilterFunction basicAuthentication(String username, String password) { - String encodedCredentials = HttpHeaders.encodeBasicAuth(username, password, null); + return basicAuthentication(username, password, null); + } + + /** + * Return a filter that applies HTTP Basic Authentication to the request + * headers via {@link HttpHeaders#setBasicAuth(String)} and + * {@link HttpHeaders#encodeBasicAuth(String, String, Charset)}. + * @param username the username + * @param password the password + * @param charset the charset to use to convert the credentials into an octet + * sequence. Defaults to {@linkplain StandardCharsets#ISO_8859_1 ISO-8859-1}. + * @return the filter to add authentication headers with + * @since 7.1 + * @see HttpHeaders#encodeBasicAuth(String, String, Charset) + * @see HttpHeaders#setBasicAuth(String) + */ + public static ExchangeFilterFunction basicAuthentication(String username, String password, @Nullable Charset charset) { + String encodedCredentials = HttpHeaders.encodeBasicAuth(username, password, charset); return (request, next) -> next.exchange(ClientRequest.from(request) .headers(headers -> headers.setBasicAuth(encodedCredentials)) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java index 70d822eaf4c4..136660d2a466 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java @@ -43,6 +43,7 @@ * Tests for {@link ExchangeFilterFunctions}. * * @author Arjen Poutsma + * @author Kai Zander */ class ExchangeFilterFunctionsTests { @@ -104,7 +105,7 @@ void basicAuthenticationUsernamePassword() { ExchangeFunction exchange = r -> { assertThat(r.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isTrue(); - assertThat(r.headers().getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); + assertThat(r.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Zm9vOmJhcg=="); return Mono.just(response); }; @@ -114,6 +115,23 @@ void basicAuthenticationUsernamePassword() { assertThat(result).isEqualTo(response); } + @Test + void basicAuthenticationUsernameAndUnicodePassword() { + ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build(); + ClientResponse response = mock(); + + ExchangeFunction exchange = r -> { + assertThat(r.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isTrue(); + assertThat(r.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic Zm9vOvCfkqk="); + return Mono.just(response); + }; + + ExchangeFilterFunction auth = ExchangeFilterFunctions.basicAuthentication("foo", "\ud83d\udca9", StandardCharsets.UTF_8); + assertThat(request.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isFalse(); + ClientResponse result = auth.filter(request, exchange).block(); + assertThat(result).isEqualTo(response); + } + @Test void basicAuthenticationInvalidCharacters() { ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build();