From 4a18488f30a382ef3bb706053e8aa83faa240a25 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 7 Aug 2018 10:10:27 +0300 Subject: [PATCH] Update deprecated basic auth client filters. 1. Update ExchangeFilterFunctions to delegate internally to HttpHeaders.setBasicAuth(user, password). 2. Remove deprecation from ExchangeFilterFunctions.basicAuthentication(String user, String password) It is still useful as a filter to insert the header. 3. Update deprecation notes. Issue: SPR-17099 --- .../org/springframework/http/HttpHeaders.java | 10 +- .../client/ExchangeFilterFunctions.java | 128 +++++++----------- .../client/ExchangeFilterFunctionsTests.java | 6 +- 3 files changed, 62 insertions(+), 82 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index dded0c19929e..5be7655f918e 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -711,8 +711,12 @@ public Set getAllow() { /** * Set the value of the {@linkplain #AUTHORIZATION Authorization} header to * Basic Authentication based on the given username and password. + *

Note that Basic Authentication only supports characters in the + * {@link StandardCharsets#ISO_8859_1 ISO-8859-1} character set. * @param username the username * @param password the password + * @throws IllegalArgumentException if either {@code user} or + * {@code password} contain characters that cannot be encoded to ISO-8859-1. * @since 5.1 * @see RFC 7617 */ @@ -725,8 +729,10 @@ public void setBasicAuth(String username, String password) { * Basic Authentication based on the given username and password. * @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} + * @param charset the charset to use to convert the credentials into an octet + * sequence. Defaults to {@linkplain StandardCharsets#ISO_8859_1 ISO-8859-1} + * @throws IllegalArgumentException if either {@code user} or + * {@code password} contain characters that cannot be encoded to ISO-8859-1. * @since 5.1 * @see RFC 7617 */ 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 f94ecb3b9a06..3b5f2210ea13 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 @@ -16,11 +16,8 @@ package org.springframework.web.reactive.function.client; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; +import java.nio.charset.Charset; import java.util.Map; -import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -46,9 +43,9 @@ public abstract class ExchangeFilterFunctions { /** - * Name of the {@linkplain ClientRequest#attributes() request attribute} that - * contains the {@link Credentials} used by {@link #basicAuthentication()}. - * @deprecated in favor of {@link HttpHeaders#setBasicAuth(String, String)} + * Name of the request attribute with {@link Credentials} for {@link #basicAuthentication()}. + * @deprecated as of Spring 5.1 in favor of using + * {@link HttpHeaders#setBasicAuth(String, String)} while building the request. */ @Deprecated public static final String BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE = @@ -72,73 +69,6 @@ public static ExchangeFilterFunction limitResponseSize(long maxByteCount) { }); } - /** - * Return a filter for HTTP Basic Authentication that adds an authorization - * header, based on the given user and password. - *

Note that Basic Authentication only supports characters in the - * {@link StandardCharsets#ISO_8859_1 ISO-8859-1} character set. - * @param user the user - * @param password the password - * @return the filter for basic authentication - * @throws IllegalArgumentException if either {@code user} or - * {@code password} contain characters that cannot be encoded to ISO-8859-1. - * @deprecated in favor of {@link HttpHeaders#setBasicAuth(String, String)} - */ - @Deprecated - public static ExchangeFilterFunction basicAuthentication(String user, String password) { - Assert.notNull(user, "'user' must not be null"); - Assert.notNull(password, "'password' must not be null"); - checkIllegalCharacters(user, password); - return basicAuthenticationInternal(request -> Optional.of(new Credentials(user, password))); - } - - /** - * Variant of {@link #basicAuthentication(String, String)} that looks up - * the {@link Credentials Credentials} provided in a - * {@linkplain ClientRequest#attributes() request attribute}, or if the - * attribute is not found, the authorization header is not added. - * @return the filter for basic authentication - * @see #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE - * @see Credentials#basicAuthenticationCredentials(String, String) - * @deprecated as of Spring 5.1, with no direct replacement - */ - @Deprecated - public static ExchangeFilterFunction basicAuthentication() { - return basicAuthenticationInternal(request -> - request.attribute(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE) - .map(credentials -> (Credentials) credentials)); - } - - @Deprecated - private static ExchangeFilterFunction basicAuthenticationInternal( - Function> credentialsFunction) { - - return ExchangeFilterFunction.ofRequestProcessor(request -> - credentialsFunction.apply(request) - .map(credentials -> Mono.just(insertAuthorizationHeader(request, credentials))) - .orElseGet(() -> Mono.just(request))); - } - - private static void checkIllegalCharacters(String username, String password) { - // Basic authentication only supports ISO 8859-1, see - // https://stackoverflow.com/questions/702629/utf-8-characters-mangled-in-http-basic-auth-username#703341 - CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); - if (!encoder.canEncode(username) || !encoder.canEncode(password)) { - throw new IllegalArgumentException( - "Username or password contains characters that cannot be encoded to ISO-8859-1"); - } - } - - private static ClientRequest insertAuthorizationHeader(ClientRequest request, Credentials credentials) { - return ClientRequest.from(request).headers(headers -> { - String credentialsString = credentials.username + ":" + credentials.password; - byte[] credentialBytes = credentialsString.getBytes(StandardCharsets.ISO_8859_1); - byte[] encodedBytes = Base64.getEncoder().encode(credentialBytes); - String encodedCredentials = new String(encodedBytes, StandardCharsets.ISO_8859_1); - headers.set(HttpHeaders.AUTHORIZATION, "Basic " + encodedCredentials); - }).build(); - } - /** * Return a filter that generates an error signal when the given * {@link HttpStatus} predicate matches. @@ -157,12 +87,55 @@ public static ExchangeFilterFunction statusError(Predicate statusPre Mono.error(exceptionFunction.apply(response)) : Mono.just(response))); } + /** + * Return a filter that applies HTTP Basic Authentication to the request + * headers via {@link HttpHeaders#setBasicAuth(String, String)}. + * @param user the user + * @param password the password + * @return the filter to add authentication headers with + * @see HttpHeaders#setBasicAuth(String, String) + * @see HttpHeaders#setBasicAuth(String, String, Charset) + */ + public static ExchangeFilterFunction basicAuthentication(String user, String password) { + return (request, next) -> + next.exchange(ClientRequest.from(request) + .headers(headers -> headers.setBasicAuth(user, password)) + .build()); + } + + + /** + * Variant of {@link #basicAuthentication(String, String)} that looks up + * the {@link Credentials Credentials} in a + * {@link #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE request attribute}. + * @return the filter to use + * @see Credentials + * @deprecated as of Spring 5.1 in favor of using + * {@link HttpHeaders#setBasicAuth(String, String)} while building the request. + */ + @Deprecated + public static ExchangeFilterFunction basicAuthentication() { + + return (request, next) -> { + Credentials cred = (Credentials) request + .attribute(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE).orElse(null); + + if (cred != null) { + return next.exchange(ClientRequest.from(request) + .headers(headers -> headers.setBasicAuth(cred.username, cred.password)) + .build()); + } + else { + return next.exchange(request); + } + }; + } + /** * Stores user and password for HTTP basic authentication. - * @see #basicAuthentication() - * @see #basicAuthenticationCredentials(String, String) - * @deprecated as of Spring 5.1, with no direct replacement + * @deprecated as of Spring 5.1 in favor of using + * {@link HttpHeaders#setBasicAuth(String, String)} while building the request. */ @Deprecated public static final class Credentials { @@ -196,7 +169,6 @@ public Credentials(String username, String password) { */ public static Consumer> basicAuthenticationCredentials(String user, String password) { Credentials credentials = new Credentials(user, password); - checkIllegalCharacters(user, password); return (map -> map.put(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE, credentials)); } 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 963fa55c05b2..9374353b98ea 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 @@ -37,6 +37,7 @@ import static org.mockito.Mockito.*; /** + * Unit tests for {@link ExchangeFilterFunctions}. * @author Arjen Poutsma */ public class ExchangeFilterFunctionsTests { @@ -94,7 +95,6 @@ public void apply() { } @Test - @SuppressWarnings("deprecation") public void basicAuthenticationUsernamePassword() { ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build(); ClientResponse response = mock(ClientResponse.class); @@ -113,8 +113,10 @@ public void basicAuthenticationUsernamePassword() { @Test(expected = IllegalArgumentException.class) public void basicAuthenticationInvalidCharacters() { + ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build(); + ExchangeFunction exchange = r -> Mono.just(mock(ClientResponse.class)); - ExchangeFilterFunctions.basicAuthentication("foo", "\ud83d\udca9"); + ExchangeFilterFunctions.basicAuthentication("foo", "\ud83d\udca9").filter(request, exchange); } @Test