Customize Spring Security Internal WebClient - spring

While working in a café hotspot Wifi, I have noticed that Spring Security is failing while resolving the JWK URI as set in application.yml:
# application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://some.vendor.com/.well-known/jwks.json
I have noticed a similar behavior with another piece of code I own that uses org.springframework.web.reactive.function.client.WebClient, which I was able to fix by changing the address resolver as in:
val jvmBlockingResolver = HttpClient.create().resolver(DefaultAddressResolverGroup.INSTANCE)
val webClient = WebClient
.builder()
.clientConnector(ReactorClientHttpConnector(jvmBlockingResolver))
.build()
Digging inside Spring Security code, I have found the following lines:
// NimbusReactiveJwtDecoder.java
package org.springframework.security.oauth2.jwt;
/* ... */
import org.springframework.web.reactive.function.client.WebClient;
/* ... */
// line 383:
source.setWebClient(this.webClient);
Now I strongly believe that this error is because of this this WebClient. Is there a way to solve this problem?
I was thinking I could inject my WebClient bean, but now that I have seen the code, I don't think that will work. Am I looking in the wrong place? Is this a Spring Security limitation or a bug?

Thanks to #Toerktumlare comment, I was able to use his suggestion to create a custom bean:
#Bean
fun reactiveJwtDecoder(): ReactiveJwtDecoder {
val jvmBlockingResolver = HttpClient.create().resolver(DefaultAddressResolverGroup.INSTANCE)
val connector = ReactorClientHttpConnector(jvmBlockingResolver)
val webClient = WebClient.builder().clientConnector(connector).build()
return NimbusReactiveJwtDecoder.withJwkSetUri("https://example.com/.well-known/jwks.json")
.webClient(webClient)
.build()
}
Also it is possible to do this with the SecurityWebFilterChain builder:
.oauth2ResourceServer {
val jvmBlockingResolver = HttpClient.create().resolver(DefaultAddressResolverGroup.INSTANCE)
val connector = ReactorClientHttpConnector(jvmBlockingResolver)
val webClient = WebClient.builder().clientConnector(connector).build()
it.jwt().jwtDecoder(
NimbusReactiveJwtDecoder
.withJwkSetUri("https://example.com/.well-known/jwks.json")
.webClient(webClient)
.build()
)
}

Related

com.nimbusds.jose.JWEHeader cannot be cast to class com.nimbusds.jose.JWSHeader

I am getting below error for spring-security using Spring Boot 2.7.3
[or-http-epoll-1] o.s.s.w.s.a.AuthenticationWebFilter:
Authentication failed: An error occurred while attempting to decode the Jwt:
class com.nimbusds.jose.JWEHeader cannot be cast to class
com.nimbusds.jose.JWSHeader (com.nimbusds.jose.JWEHeader and
com.nimbusds.jose.JWSHeader are in unnamed module of loader
org.springframework.boot.loader.LaunchedURLClassLoader #43da41e)
I suspect the following cast inside this method is causing the error:
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.JwtValidators
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders
#Bean
fun jwtDecoder(properties: OAuth2ResourceServerProperties): ReactiveJwtDecoder {
val issuerUri = properties.jwt.issuerUri
val jwtDecoder = ReactiveJwtDecoders.fromOidcIssuerLocation(issuerUri) as NimbusReactiveJwtDecoder
val audienceValidator: OAuth2TokenValidator<Jwt> = AudienceValidator(audience)
val withIssuer: OAuth2TokenValidator<Jwt> = JwtValidators.createDefaultWithIssuer(issuerUri)
val withAudience: OAuth2TokenValidator<Jwt> = DelegatingOAuth2TokenValidator(withIssuer, audienceValidator)
jwtDecoder.setJwtValidator(withAudience)
return jwtDecoder
}
We're using Auth0 as a provider.
Turns out we were sending an Opaque Access token according to Auth0. We needed to specify the audience on our ApiProvider for React Frontend:
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={window.location.origin}
onRedirectCallback={onRedirectCallback}
audience={auth0Audience}
>
Also needed to disable RDBAC as we're not using Scoped APIs:
Lastly jwt.io was a great tool in testing our JWT tokens. We were expecting RS256 Algorithm typed tokens, but were getting DIR types when getting opaque tokens when testing against it.

Get Spring Default Feign Options on programmatically created feign client

I am trying to get the default feign options/config to use it in feign clients that I create programatically using Feign.Builder:
This is the config (application.yaml) that I want to get:
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 5000
What I tried to do is to do is to add #EnableFeignClients and try to get the FactoryBean<Request.Options> or it's implementation OptionsFactoryBean but I don't see it being injected by Spring anywhere.
I've also tried searching in StackOverflow and other websites to see if there are other people that have tried what I am trying to do, but I wasn't able to find any information hence why I am creating this question.
public MyClientFactory(Client client,
ObjectMapper objectMapper,
FactoryBean<Request.Options> optionsFactory,
ErrorDecoder errorDecoder) throws Exception {
this.builder = Feign.builder()
.client(client)
.decoder(new JacksonDecoder(objectMapper))
.encoder(new JacksonEncoder(objectMapper))
.options(optionsFactory.getObject())
.errorDecoder(errorDecoder);
}
Can someone please let me know how I can get the default spring feign configs? Maybe my approach is incorrect?
Thanks!
Haven't tried it but I assume you'll need a bean from the FeignClientProperties class.
I ended up creating a Request.Options #Bean based on FeignClientProperties as #Arnold Galovics suggested.
#Bean
Request.Options options(FeignClientProperties feignClientProperties) {
FeignClientProperties.FeignClientConfiguration feignClientConfiguration = feignClientProperties.getConfig().get(feignClientProperties.getDefaultConfig());
Request.Options defaultOptions = new Request.Options();
return new Request.Options(
Optional.ofNullable(feignClientConfiguration.getConnectTimeout()).orElse(defaultOptions.connectTimeoutMillis()), TimeUnit.MILLISECONDS,
Optional.ofNullable(feignClientConfiguration.getReadTimeout()).orElse(defaultOptions.readTimeoutMillis()), TimeUnit.MILLISECONDS,
Optional.ofNullable(feignClientConfiguration.isFollowRedirects()).orElse(defaultOptions.isFollowRedirects())
);
}
And then I injected this bean in my Feign Client Factory:
public MyClientFactory(Client client,
ObjectMapper objectMapper,
Request.Options options,
ErrorDecoder errorDecoder) {
this.builder = Feign.builder()
.client(client)
.decoder(new JacksonDecoder(objectMapper))
.encoder(new JacksonEncoder(objectMapper))
.options(options)
.errorDecoder(errorDecoder);
}

How do I make spring boot RestTemplate client metrics not create a new tag for query parameters

I've got a spring boot application that is defining a RestTemplate bean as follows:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
Also, pulling in spring-boot-starter-actuator and io.micrometer:micrometer-registry-prometheus.
When I use the injected RestTemplate as follows:
#Autowired
private RestTemplate restTemplate;
private String uriTemplate = "http://my.domain.com/bookstore-api/books";
public List<Book> getBooksByAuthor(String author) {
// create URI for "http://my.domain.com/bookstore-api/books?author={authorId}"
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
.fromUriString(uriTemplate)
.queryParam("author", author);
// make the GET
ResponseEntity<Book[]> responseEntity = restTemplate.getForEntity(uriComponentsBuilder.toUriString(),Book[].class);
// rest ommitted for brevity
}
When getBooksByAuthor("Tolkien") is called, we can then hit /metrics/prometheus and see the following:
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 0.253227898
This would be fine, except that there are lots of authors out there, and eventually I will get the "too many tags" exception.
I would prefer to have the following (similar to how path variables get templated):
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 0.253227898
Is this possible to achieve by modifying the way I use UriComponentsBuilder? The closest thing I've found is to define my own RestTemplateExchangeTagsProvider, and override the default to do some crazy regex replacement.
Just fixed same issue in SpringBoot 2.4.5 using:
responseEntity = restTemplate.exchange(
config.getDataUrl(),
HttpMethod.GET,
httpEntity,
new ParameterizedTypeReference<String>() {},
rowId);
where getDataUrl resolves to:
https://data-service-dev.apps.cloud.net/api/hbase/getData?rowId={rowId}
metrics:
http_client_requests_seconds_count{clientName="data-service-dev.apps.cloud.net",method="GET",outcome="SUCCESS",status="200",uri="/api/hbase/getData?rowId={rowId}",} 1.0
...
I have had a same problem. Maybe this information will help you.
In my case restTemplate.setUriTemplateHandler(handler) had overwritten annonymous
MetricsClientHttpRequestInterceptor#createUriTemplateHandler.
And the original url templates had not been stored into memory for prometheus.
DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
builderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
customizer.customize(restTemplate);
restTemplate.setUriTemplateHandler(handler);
So,
I changed order of the commands:
restTemplate.setUriTemplateHandler(handler);
customizer.customize(restTemplate);
Please check that there are no setting commands for restTemplate after MetricsClientHttpRequestInterceptor.customize(restTemplate).

Spring Security 5 rest client with OAuth2

I would like to implement a client which should simply send some rest calls with a OAuth2 token. Using spring-security-oauth it was pretty easy to use the OAuth2RestTemplate with a client-credentials flow. Today I saw most of those classes are deprecated in 2.4.0 and the recommendation is to use Spring Security 5. I've googled around and looked into the Migration Guide [1] but I've not understood what I've to do to perform some simple rest call which fetches a token with Spring Security 5. I think I'm even not sure what kind of libraries are needed. So what I'm basically is looking for is a way to provide a client-id, client-secret and a tokenendpoint programatically (not via properties) to some kind of rest template and send a request to a specific url.
--edit--
I found a way of using the web client without using the properties but rather using the ClientRegestration object. I'm not sure if that is a recommended way:
#Test
public void test() {
WebClient webClient = getWebClient();
ResponseSpec retrieve = webClient.get().uri("https://somepath")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(REG_ID)).retrieve();
Flux<String> result = retrieve.bodyToFlux(String.class); // flux makes no sense here, use Mono instead
Mono<List<String>> response = result.collectList();
List<String> block = response.block();
System.out.print(block);
System.out.print("debug");
}
public WebClient getWebClient() {
Builder clientRegestrationBuilder = ClientRegistration.withRegistrationId(REG_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
ClientRegistration clientRegistration = clientRegestrationBuilder.build();
ReactiveClientRegistrationRepository repo = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(repo,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}
Regards
monti
[1] https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
The following code is a unit test which shows how a ClientRegistration can be done programatically. In "real" spring scenario I guess the ClientRegistration should be provided as bean and finally injected as a list to a ReactiveClientRegistrationRepository...
public void test() {
WebClient webClient = getWebClient();
ResponseSpec retrieve = webClient.get().uri("https://somepath")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(REG_ID)).retrieve();
Flux<String> result = retrieve.bodyToFlux(String.class); // flux makes no sense here, use Mono instead
Mono<List<String>> response = result.collectList();
List<String> block = response.block();
System.out.print(block);
System.out.print("debug");
}
public WebClient getWebClient() {
Builder clientRegestrationBuilder = ClientRegistration.withRegistrationId(REG_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
ClientRegistration clientRegistration = clientRegestrationBuilder.build();
ReactiveClientRegistrationRepository repo = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(repo,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}

Spring Boot add additional attribute to WebClient request in ServerOAuth2AuthorizedClientExchangeFilterFunction

I am trying to implement the client_credentials grant to get a token in my spring boot resource server.
I am using Auth0 as an Authorization server. They seem to require an extra parameter in the request body to be added called audience.
I have tried to do the request through postman and it works. I am now trying to reproduce it within Spring. Here is the working postman request
curl -X POST \
https://XXX.auth0.com/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&audience=https%3A%2F%2Fxxxxx.auth0.com%2Fapi%2Fv2%2F&client_id=SOME_CLIENT_ID&client_secret=SOME_CLIENT_SECRET'
The problem I am facing is that i have no way to add the missing audience parameter to the token request.
I have a configuration defined in my application.yml
client:
provider:
auth0:
issuer-uri: https://XXXX.auth0.com//
registration:
auth0-client:
provider: auth0
client-id: Client
client-secret: Secret
authorization_grant_type: client_credentials
auth0:
client-id: Client
client-secret: Secret
I have the web client filter configured like this.
#Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
ServerOAuth2AuthorizedClientRepository authorizedClients) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrations, authorizedClients);
oauth2.setDefaultClientRegistrationId("auth0");
return WebClient.builder()
.filter(oauth2)
.build();
}
I am injecting the instance and trying to do a request to get the user by email
return this.webClient.get()
.uri(this.usersUrl + "/api/v2/users-by-email?email={email}", email)
.attributes(auth0ClientCredentials())
.retrieve()
.bodyToMono(User.class);
The way i understand it, the filter intercepts this userByEmail request and before it executes it it tries to execute the /oauth/token request to get JWT Bearer token which it can append to the first one and execute it.
Is there a way to add a parameter to the filter? It has been extremely difficult to step through it and figure out where exactly the parameters are being appended since its reactive and am quite new at this. Even some pointers to where to look would be helpful.
I was having the same problem where access token response and request for it wasn't following oAuth2 standards. Here's my code (it's in kotlin but should be understandable also for java devs) for spring boot version 2.3.6.RELEASE.
Gradle dependencies:
implementation(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}"))
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
After adding them you have to firstly create your custom token request/response client which will implement ReactiveOAuth2AccessTokenResponseClient interface:
class CustomTokenResponseClient : ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
private val webClient = WebClient.builder().build()
override fun getTokenResponse(
authorizationGrantRequest: OAuth2ClientCredentialsGrantRequest
): Mono<OAuth2AccessTokenResponse> =
webClient.post()
.uri(authorizationGrantRequest.clientRegistration.providerDetails.tokenUri)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.bodyValue(CustomTokenRequest(
clientId = authorizationGrantRequest.clientRegistration.clientId,
clientSecret = authorizationGrantRequest.clientRegistration.clientSecret
))
.exchange()
.flatMap { it.bodyToMono<NotStandardTokenResponse>() }
.map { it.toOAuth2AccessTokenResponse() }
private fun NotStandardTokenResponse.toOAuth2AccessTokenResponse() = OAuth2AccessTokenResponse
.withToken(this.accessToken)
.refreshToken(this.refreshToken)
.expiresIn(convertExpirationDateToDuration(this.data.expires).toSeconds())
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.build()
}
As you can see above, in this class you can adjust token request/response handling to your specific needs.
Note: authorizationGrantRequest param inside getTokenResponse method. Spring is passing here data from you application properties, so follow the standards when defining them, e.g. they may look like this:
spring:
security:
oauth2:
client:
registration:
name-for-oauth-integration:
authorization-grant-type: client_credentials
client-id: id
client-secret: secret
provider:
name-for-oauth-integration:
token-uri: https://oauth.com/token
The last step is to use your CustomTokenResponseClient inside oAuth2 configuration, it may look like this:
#Configuration
class CustomOAuth2Configuration {
#Bean
fun customOAuth2WebWebClient(clientRegistrations: ReactiveClientRegistrationRepository): WebClient {
val clientRegistryRepo = InMemoryReactiveClientRegistrationRepository(
clientRegistrations.findByRegistrationId("name-for-oauth-integration").block()
)
val clientService = InMemoryReactiveOAuth2AuthorizedClientService(clientRegistryRepo)
val authorizedClientManager =
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistryRepo, clientService)
val authorizedClientProvider = ClientCredentialsReactiveOAuth2AuthorizedClientProvider()
authorizedClientProvider.setAccessTokenResponseClient(CustomTokenResponseClient())
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
val oauthFilter = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauthFilter.setDefaultClientRegistrationId("name-for-oauth-integration")
return WebClient.builder()
.filter(oauthFilter)
.build()
}
}
Right now, this is possible, but not elegant.
Note that you can provide a custom ReactiveOAuth2AccessTokenResponseClient to ServerOAuth2AuthorizedClientExchangeFilterFunction.
You can create your own implementation of this - and thereby add any other parameters you need - by copying the contents of WebClientReactiveClientCredentialsTokenResponseClient.
That said, it would be better if there were a setter to make that more convenient. You can follow the corresponding issue in Spring Security's backlog.
Here is what i found out after further investigation. The code described in my question was never going to call the client_credentials and fit my use-case. I think (not 100% sure on this) it will be very useful in the future if i am trying to propagate the user submitted token around multiple services in a micro-service architecture. A chain of actions like this comes to mind:
User calls Service A -> Service A calls Service B -> Service B responds -> Service A responds back to user request.
And using the same token to begin with through the whole process.
My solution to my use-case:
What i did was create a new Filter class largely based on the original and implement a step before executing the request where i check if i have a JWT token stored that can be used for the Auth0 Management API. If i don't i build up the client_credentials grant request and get one, then attach this token as a bearer to the initial request and execute that one. I also added a small token in-memory caching mechanism so that if the token is valid any other requests at a later date will just use it. Here is my code.
Filter
public class Auth0ClientCredentialsGrantFilterFunction implements ExchangeFilterFunction {
private ReactiveClientRegistrationRepository clientRegistrationRepository;
/**
* Required by auth0 when requesting a client credentials token
*/
private String audience;
private String clientRegistrationId;
private Auth0InMemoryAccessTokenStore auth0InMemoryAccessTokenStore;
public Auth0ClientCredentialsGrantFilterFunction(ReactiveClientRegistrationRepository clientRegistrationRepository,
String clientRegistrationId,
String audience) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.audience = audience;
this.clientRegistrationId = clientRegistrationId;
this.auth0InMemoryAccessTokenStore = new Auth0InMemoryAccessTokenStore();
}
public void setAuth0InMemoryAccessTokenStore(Auth0InMemoryAccessTokenStore auth0InMemoryAccessTokenStore) {
this.auth0InMemoryAccessTokenStore = auth0InMemoryAccessTokenStore;
}
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return auth0ClientCredentialsToken(next)
.map(token -> bearer(request, token.getTokenValue()))
.flatMap(next::exchange)
.switchIfEmpty(next.exchange(request));
}
private Mono<OAuth2AccessToken> auth0ClientCredentialsToken(ExchangeFunction next) {
return Mono.defer(this::loadClientRegistration)
.map(clientRegistration -> new ClientCredentialsRequest(clientRegistration, audience))
.flatMap(request -> this.auth0InMemoryAccessTokenStore.retrieveToken()
.switchIfEmpty(refreshAuth0Token(request, next)));
}
private Mono<OAuth2AccessToken> refreshAuth0Token(ClientCredentialsRequest clientCredentialsRequest, ExchangeFunction next) {
ClientRegistration clientRegistration = clientCredentialsRequest.getClientRegistration();
String tokenUri = clientRegistration
.getProviderDetails().getTokenUri();
ClientRequest clientCredentialsTokenRequest = ClientRequest.create(HttpMethod.POST, URI.create(tokenUri))
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.body(clientCredentialsTokenBody(clientCredentialsRequest))
.build();
return next.exchange(clientCredentialsTokenRequest)
.flatMap(response -> response.body(oauth2AccessTokenResponse()))
.map(OAuth2AccessTokenResponse::getAccessToken)
.doOnNext(token -> this.auth0InMemoryAccessTokenStore.storeToken(token));
}
private static BodyInserters.FormInserter<String> clientCredentialsTokenBody(ClientCredentialsRequest clientCredentialsRequest) {
ClientRegistration clientRegistration = clientCredentialsRequest.getClientRegistration();
return BodyInserters
.fromFormData("grant_type", AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.with("client_id", clientRegistration.getClientId())
.with("client_secret", clientRegistration.getClientSecret())
.with("audience", clientCredentialsRequest.getAudience());
}
private Mono<ClientRegistration> loadClientRegistration() {
return Mono.just(clientRegistrationId)
.flatMap(r -> clientRegistrationRepository.findByRegistrationId(r));
}
private ClientRequest bearer(ClientRequest request, String token) {
return ClientRequest.from(request)
.headers(headers -> headers.setBearerAuth(token))
.build();
}
static class ClientCredentialsRequest {
private final ClientRegistration clientRegistration;
private final String audience;
public ClientCredentialsRequest(ClientRegistration clientRegistration, String audience) {
this.clientRegistration = clientRegistration;
this.audience = audience;
}
public ClientRegistration getClientRegistration() {
return clientRegistration;
}
public String getAudience() {
return audience;
}
}
}
Token Store
public class Auth0InMemoryAccessTokenStore implements ReactiveInMemoryAccessTokenStore {
private AtomicReference<OAuth2AccessToken> token = new AtomicReference<>();
private Clock clock = Clock.systemUTC();
private Duration accessTokenExpiresSkew = Duration.ofMinutes(1);
public Auth0InMemoryAccessTokenStore() {
}
#Override
public Mono<OAuth2AccessToken> retrieveToken() {
return Mono.justOrEmpty(token.get())
.filter(Objects::nonNull)
.filter(token -> token.getExpiresAt() != null)
.filter(token -> {
Instant now = this.clock.instant();
Instant expiresAt = token.getExpiresAt();
if (now.isBefore(expiresAt.minus(this.accessTokenExpiresSkew))) {
return true;
}
return false;
});
}
#Override
public Mono<Void> storeToken(OAuth2AccessToken token) {
this.token.set(token);
return Mono.empty();
}
}
Token Store Interface
public interface ReactiveInMemoryAccessTokenStore {
Mono<OAuth2AccessToken> retrieveToken();
Mono<Void> storeToken(OAuth2AccessToken token);
}
And finally defining the beans and using it.
#Bean
public Auth0ClientCredentialsGrantFilterFunction auth0FilterFunction(ReactiveClientRegistrationRepository clientRegistrations,
#Value("${auth0.client-registration-id}") String clientRegistrationId,
#Value("${auth0.audience}") String audience) {
return new Auth0ClientCredentialsGrantFilterFunction(clientRegistrations, clientRegistrationId, audience);
}
#Bean(name = "auth0-webclient")
WebClient webClient(Auth0ClientCredentialsGrantFilterFunction filter) {
return WebClient.builder()
.filter(filter)
.build();
}
There is a slight problem with the token store at this time as the client_credentials token request will be executed multiple on parallel requests that come at the same time, but i can live with that for the foreseeable future.
Your application.yml is missing one variable:
client-authentication-method: post
it should be like this:
spring:
security:
oauth2:
client:
provider:
auth0-client:
token-uri: https://XXXX.auth0.com//
registration:
auth0-client:
client-id: Client
client-secret: Secret
authorization_grant_type: client_credentials
client-authentication-method: post
Without it I was getting "invalid_client" response all the time.
Tested in spring-boot 2.7.2

Resources