|
|
@ -32,8 +32,8 @@ import org.springframework.http.HttpStatus; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Implementations of {@link ExchangeFilterFunction} that provide various useful request filter |
|
|
|
* Static factory methods providing access to built-in implementations of |
|
|
|
* operations, such as basic authentication, error handling, etc. |
|
|
|
* {@link ExchangeFilterFunction} for basic authentication, error handling, etc. |
|
|
|
* |
|
|
|
* |
|
|
|
* @author Rob Winch |
|
|
|
* @author Rob Winch |
|
|
|
* @author Arjen Poutsma |
|
|
|
* @author Arjen Poutsma |
|
|
@ -42,93 +42,84 @@ import org.springframework.util.Assert; |
|
|
|
public abstract class ExchangeFilterFunctions { |
|
|
|
public abstract class ExchangeFilterFunctions { |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Name of the {@link ClientRequest} attribute that contains the |
|
|
|
* Name of the {@linkplain ClientRequest#attributes() request attribute} that |
|
|
|
* {@link Credentials}, as used by {@link #basicAuthentication()}. |
|
|
|
* contains the {@link Credentials} used by {@link #basicAuthentication()}. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static final String BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE = |
|
|
|
public static final String BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE = |
|
|
|
ExchangeFilterFunctions.class.getName() + ".basicAuthenticationCredentials"; |
|
|
|
ExchangeFilterFunctions.class.getName() + ".basicAuthenticationCredentials"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Return a filter that adds an Authorization header for HTTP Basic Authentication, based on |
|
|
|
* Return a filter for HTTP Basic Authentication that adds an authorization |
|
|
|
* the given username and password. |
|
|
|
* header, based on the given user and password. |
|
|
|
* <p>Note that Basic Authentication only supports characters in the |
|
|
|
* <p>Note that Basic Authentication only supports characters in the |
|
|
|
* {@link StandardCharsets#ISO_8859_1 ISO-8859-1} character set. |
|
|
|
* {@link StandardCharsets#ISO_8859_1 ISO-8859-1} character set. |
|
|
|
* @param username the username to use |
|
|
|
* @param user the user |
|
|
|
* @param password the password to use |
|
|
|
* @param password the password |
|
|
|
* @return the {@link ExchangeFilterFunction} that adds the Authorization header |
|
|
|
* @return the filter for basic authentication |
|
|
|
* @throws IllegalArgumentException if either {@code username} or {@code password} contain |
|
|
|
* @throws IllegalArgumentException if either {@code user} or |
|
|
|
* characters that cannot be encoded to ISO-8859-1 |
|
|
|
* {@code password} contain characters that cannot be encoded to ISO-8859-1. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static ExchangeFilterFunction basicAuthentication(String username, String password) { |
|
|
|
public static ExchangeFilterFunction basicAuthentication(String user, String password) { |
|
|
|
Assert.notNull(username, "'username' must not be null"); |
|
|
|
Assert.notNull(user, "'user' must not be null"); |
|
|
|
Assert.notNull(password, "'password' must not be null"); |
|
|
|
Assert.notNull(password, "'password' must not be null"); |
|
|
|
checkIllegalCharacters(username, password); |
|
|
|
checkIllegalCharacters(user, password); |
|
|
|
return basicAuthenticationInternal(r -> Optional.of(new Credentials(username, password))); |
|
|
|
return basicAuthenticationInternal(request -> Optional.of(new Credentials(user, password))); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Return a filter that adds an Authorization header for HTTP Basic Authentication, based on |
|
|
|
* Variant of {@link #basicAuthentication(String, String)} that looks up |
|
|
|
* the {@link Credentials} provided in the |
|
|
|
* the {@link Credentials Credentials} provided in a |
|
|
|
* {@linkplain ClientRequest#attributes() request attributes}. If the attribute is not found, |
|
|
|
* {@linkplain ClientRequest#attributes() request attribute}, or if the |
|
|
|
* no authorization header is added. |
|
|
|
* attribute is not found, the authorization header is not added. |
|
|
|
* <p>Note that Basic Authentication only supports characters in the |
|
|
|
* @return the filter for basic authentication |
|
|
|
* {@link StandardCharsets#ISO_8859_1 ISO-8859-1} character set. |
|
|
|
|
|
|
|
* @return the {@link ExchangeFilterFunction} that adds the Authorization header |
|
|
|
|
|
|
|
* @see #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE |
|
|
|
* @see #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE |
|
|
|
* @see Credentials#basicAuthenticationCredentials(String, String) |
|
|
|
* @see Credentials#basicAuthenticationCredentials(String, String) |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static ExchangeFilterFunction basicAuthentication() { |
|
|
|
public static ExchangeFilterFunction basicAuthentication() { |
|
|
|
return basicAuthenticationInternal( |
|
|
|
return basicAuthenticationInternal(request -> |
|
|
|
request -> request.attribute(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE).map(o -> (Credentials)o)); |
|
|
|
request.attribute(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE) |
|
|
|
|
|
|
|
.map(credentials -> (Credentials) credentials)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static ExchangeFilterFunction basicAuthenticationInternal( |
|
|
|
private static ExchangeFilterFunction basicAuthenticationInternal( |
|
|
|
Function<ClientRequest, Optional<Credentials>> credentialsFunction) { |
|
|
|
Function<ClientRequest, Optional<Credentials>> credentialsFunction) { |
|
|
|
|
|
|
|
|
|
|
|
return ExchangeFilterFunction.ofRequestProcessor( |
|
|
|
return ExchangeFilterFunction.ofRequestProcessor(request -> |
|
|
|
clientRequest -> credentialsFunction.apply(clientRequest).map( |
|
|
|
credentialsFunction.apply(request) |
|
|
|
credentials -> { |
|
|
|
.map(credentials -> Mono.just(insertAuthorizationHeader(request, credentials))) |
|
|
|
ClientRequest authorizedRequest = ClientRequest.from(clientRequest) |
|
|
|
.orElse(Mono.just(request))); |
|
|
|
.headers(headers -> headers.set(HttpHeaders.AUTHORIZATION, |
|
|
|
|
|
|
|
authorization(credentials))) |
|
|
|
|
|
|
|
.build(); |
|
|
|
|
|
|
|
return Mono.just(authorizedRequest); |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
.orElse(Mono.just(clientRequest))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static String authorization(Credentials credentials) { |
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
return "Basic " + encodedCredentials; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
* Basic authentication only supports ISO 8859-1, see |
|
|
|
|
|
|
|
* https://stackoverflow.com/questions/702629/utf-8-characters-mangled-in-http-basic-auth-username#703341
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private static void checkIllegalCharacters(String username, String password) { |
|
|
|
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(); |
|
|
|
CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); |
|
|
|
if (!encoder.canEncode(username) || !encoder.canEncode(password)) { |
|
|
|
if (!encoder.canEncode(username) || !encoder.canEncode(password)) { |
|
|
|
throw new IllegalArgumentException( |
|
|
|
throw new IllegalArgumentException( |
|
|
|
"Username or password contains characters that cannot be encoded to ISO-8859-1"); |
|
|
|
"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 returns a given {@link Throwable} as response if the given |
|
|
|
* Return a filter that generates an error signal when the given |
|
|
|
* {@link HttpStatus} predicate matches. |
|
|
|
* {@link HttpStatus} predicate matches. |
|
|
|
* @param statusPredicate the predicate that should match the |
|
|
|
* @param statusPredicate the predicate to check the HTTP status with |
|
|
|
* {@linkplain ClientResponse#statusCode() response status} |
|
|
|
* @param exceptionFunction the function that to create the exception |
|
|
|
* @param exceptionFunction the function that returns the exception |
|
|
|
* @return the filter to generate an error signal |
|
|
|
* @return the {@link ExchangeFilterFunction} that returns the given exception if the predicate |
|
|
|
|
|
|
|
* matches |
|
|
|
|
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static ExchangeFilterFunction statusError(Predicate<HttpStatus> statusPredicate, |
|
|
|
public static ExchangeFilterFunction statusError(Predicate<HttpStatus> statusPredicate, |
|
|
|
Function<ClientResponse, ? extends Throwable> exceptionFunction) { |
|
|
|
Function<ClientResponse, ? extends Throwable> exceptionFunction) { |
|
|
@ -137,20 +128,16 @@ public abstract class ExchangeFilterFunctions { |
|
|
|
Assert.notNull(exceptionFunction, "Function must not be null"); |
|
|
|
Assert.notNull(exceptionFunction, "Function must not be null"); |
|
|
|
|
|
|
|
|
|
|
|
return ExchangeFilterFunction.ofResponseProcessor( |
|
|
|
return ExchangeFilterFunction.ofResponseProcessor( |
|
|
|
clientResponse -> { |
|
|
|
response -> statusPredicate.test(response.statusCode()) ? |
|
|
|
if (statusPredicate.test(clientResponse.statusCode())) { |
|
|
|
Mono.error(exceptionFunction.apply(response)) : |
|
|
|
return Mono.error(exceptionFunction.apply(clientResponse)); |
|
|
|
Mono.just(response) |
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
return Mono.just(clientResponse); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Represents a combination of username and password, as used by {@link #basicAuthentication()}. |
|
|
|
* Stores user and password for HTTP basic authentication. |
|
|
|
|
|
|
|
* @see #basicAuthentication() |
|
|
|
* @see #basicAuthenticationCredentials(String, String) |
|
|
|
* @see #basicAuthenticationCredentials(String, String) |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static final class Credentials { |
|
|
|
public static final class Credentials { |
|
|
@ -159,6 +146,7 @@ public abstract class ExchangeFilterFunctions { |
|
|
|
|
|
|
|
|
|
|
|
private final String password; |
|
|
|
private final String password; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Create a new {@code Credentials} instance with the given username and password. |
|
|
|
* Create a new {@code Credentials} instance with the given username and password. |
|
|
|
* @param username the username |
|
|
|
* @param username the username |
|
|
@ -171,23 +159,25 @@ public abstract class ExchangeFilterFunctions { |
|
|
|
this.password = password; |
|
|
|
this.password = password; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Return a consumer that stores the given username and password in the |
|
|
|
* Return a {@literal Consumer} that stores the given user and password |
|
|
|
* {@linkplain ClientRequest.Builder#attributes(java.util.function.Consumer) request |
|
|
|
* as a request attribute of type {@code Credentials} that is in turn |
|
|
|
* attributes} as a {@code Credentials} object. |
|
|
|
* used by {@link ExchangeFilterFunctions#basicAuthentication()}. |
|
|
|
* @param username the username |
|
|
|
* @param user the user |
|
|
|
* @param password the password |
|
|
|
* @param password the password |
|
|
|
* @return a consumer that adds the given credentials to the attribute map |
|
|
|
* @return a consumer that can be passed into |
|
|
|
|
|
|
|
* {@linkplain ClientRequest.Builder#attributes(java.util.function.Consumer)} |
|
|
|
* @see ClientRequest.Builder#attributes(java.util.function.Consumer) |
|
|
|
* @see ClientRequest.Builder#attributes(java.util.function.Consumer) |
|
|
|
* @see #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE |
|
|
|
* @see #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static Consumer<Map<String, Object>> basicAuthenticationCredentials(String username, String password) { |
|
|
|
public static Consumer<Map<String, Object>> basicAuthenticationCredentials(String user, String password) { |
|
|
|
Credentials credentials = new Credentials(username, password); |
|
|
|
Credentials credentials = new Credentials(user, password); |
|
|
|
checkIllegalCharacters(username, password); |
|
|
|
checkIllegalCharacters(user, password); |
|
|
|
|
|
|
|
return map -> map.put(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE, credentials); |
|
|
|
return attributes -> attributes.put(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE, credentials); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public boolean equals(Object o) { |
|
|
|
public boolean equals(Object o) { |
|
|
|
if (this == o) { |
|
|
|
if (this == o) { |
|
|
@ -197,7 +187,6 @@ public abstract class ExchangeFilterFunctions { |
|
|
|
Credentials other = (Credentials) o; |
|
|
|
Credentials other = (Credentials) o; |
|
|
|
return this.username.equals(other.username) && |
|
|
|
return this.username.equals(other.username) && |
|
|
|
this.password.equals(other.password); |
|
|
|
this.password.equals(other.password); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
@ -206,7 +195,6 @@ public abstract class ExchangeFilterFunctions { |
|
|
|
public int hashCode() { |
|
|
|
public int hashCode() { |
|
|
|
return 31 * this.username.hashCode() + this.password.hashCode(); |
|
|
|
return 31 * this.username.hashCode() + this.password.hashCode(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|