Custom Authorization header in Reactive Spring Security OAuth2
2024 / 10 / 30 • Daniel Garnier-Moiroux
Someone asked me a Spring Security question today, along the lines of:
When using Spring Security OAuth2, in reactive apps, we use the WebClient integration Is it possible to change the header that the
ServerOAuth2AuthorizedClientExchangeFilterFunction
uses? By default it usesAuthorization: Bearer <access_token>
, but we’d like to drop theBearer
part for **reasons**, and sendAuthorization: <access_token>
instead.
The answer is no, you can’t customized the header used in
ServerOAuth2AuthorizedClientExchangeFilterFunction
(what a mouthful, let’s call it oauth-EFF),
because it is
hardcoded,
to follow RFC6750, specifically 2.1. Authorization Request Header
Field.
My first recommendation would be to update the resource servers to be spec compliant, and access the
specified header format, Authorization: Bearer <access_token>
. If you have legacy clients that
cannot be updated, you could imagine a world where your resource server supports the OAuth2 spec,
and your legacy non-Bearer tokens.
If that is not possible, fret not, there is a way around it: you can make your own
ExchangeFilterFunction
, that “updates” the value of the Authorization
header:
import reactor.core.publisher.Mono;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
class CustomAuthorizationExchangeFilterFunction implements ExchangeFilterFunction {
@Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return Mono.just(request).map(req -> {
// Grab the original authorization header created by Spring Security
var originalHeader = req.headers().getFirst("Authorization");
// Extract the token from the header
var token = originalHeader.toLowerCase().substring("bearer ".length());
return ClientRequest.from(req)
// Remove the existing header entirely ; because calling ".header" would add an additional
// header value, rather than replace the existing value
.headers(h -> h.remove("Authorization"))
// Set the Authorization header to the desired value
.header("Authorization", token)
.build();
}).flatMap(next::exchange);
}
}
Then you can use it in addition to the oauth-EFF, after the oauth-EFF is applied. This way, all the token management is handled by the oauth-EFF, and you just sprinkle some magic over the headers:
@Bean
WebClient customHeaderWebClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2ClientFilterFunction =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(oauth2ClientFilterFunction)
.filter(new CustomAuthorizationExchangeFilterFunction())
.build();
}
Tadaaaa 🎉️