Skip to content

Commit

Permalink
Update deprecated basic auth client filters.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rstoyanchev committed Aug 7, 2018
1 parent 6b82a6c commit 4a18488
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 82 deletions.
Expand Up @@ -711,8 +711,12 @@ public Set<HttpMethod> getAllow() {
/**
* Set the value of the {@linkplain #AUTHORIZATION Authorization} header to
* Basic Authentication based on the given username and password.
* <p>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 <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
*/
Expand All @@ -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 <a href="https://tools.ietf.org/html/rfc7617">RFC 7617</a>
*/
Expand Down
Expand Up @@ -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;
Expand All @@ -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 =
Expand All @@ -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.
* <p>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<ClientRequest, Optional<Credentials>> 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.
Expand All @@ -157,12 +87,55 @@ public static ExchangeFilterFunction statusError(Predicate<HttpStatus> 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 {
Expand Down Expand Up @@ -196,7 +169,6 @@ public Credentials(String username, String password) {
*/
public static Consumer<Map<String, Object>> basicAuthenticationCredentials(String user, String password) {
Credentials credentials = new Credentials(user, password);
checkIllegalCharacters(user, password);
return (map -> map.put(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE, credentials));
}

Expand Down
Expand Up @@ -37,6 +37,7 @@
import static org.mockito.Mockito.*;

/**
* Unit tests for {@link ExchangeFilterFunctions}.
* @author Arjen Poutsma
*/
public class ExchangeFilterFunctionsTests {
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down

0 comments on commit 4a18488

Please sign in to comment.