Spring Security multiple calls to different OAuth servers requiring multiple tokens in SecurityContext

Viewed 1098

I have a spring application that verifies a JWT token on the rest endpoint.

Using SecurityChain

.oAuth2ResourceServer()
.jwt()

This seems to create a JwtAuthenticationToken in the ReactiveSecurityContextHolder.

I then want to flow the input from this endpoint where the client is authenticated by checking the bearer token. And then call another rest service using a webClient. This web client needs to authenticate with grant type password with the external service using a different OAuth server and get is own bearer token.

The problem is that the web client uses the ReactiveSecurityContextHolder that contains the authenticated JWT. And tries to use this information rather than connect and authenticate my app to the rest endpoint.

I have set up the Yaml to register my client

spring:
   security:
     oauth2:
       client:
         registration:
           Myapp:
             client-id:
             client-secret:
             token-uri:
             authorization-grant-type:

Then adding a filter function of

ServerOAuth2AuthorizedClientExchangeFilterFunction

But I get principalName cannot be empty as it seems to reuse the security context from verifying the caller on the rest endpoint in my application.

How should it be designed or samples to show how you can use different security contexts or get tokens differently between service to service calls?

1 Answers

You are correct that the design of ServerOAuth2AuthorizedClientExchangeFilterFunction is designed to be based on the currently-authorized client, which you've explained that you don't want to use in this case.

You've indicated that you want to use the client's credentials as the username and password for the Resource Owner Password Grant. However, there's nothing in Spring Security that is going to do that.

However, you can use WebClientReactivePasswordTokenResponseClient directly in order to formulate the custom request yourself.

Briefly, this would be a custom ExchangeFilterFunction that would look something like:

ClientRegistrationRespository clientRegistrations;
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> 
        accessTokenResponseClient = new WebClientReactivePasswordTokenResponseClient();

Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
    return this.clientRegistrations.findByRegistrationId("registration-id")
        .map(clientRegistration -> new OAuth2PasswordGrantRequest(
                clientRegistration, 
                clientRegistration.getClientId(),
                clientRegistration.getClientSecret())
        .map(this.accessTokenResponseClient::getTokenResponse)
        .map(tokenResponse -> ClientRequest.from(request)
            .headers(h -> h.setBearerAuth(tokenResponse.getAccessToken().getTokenValue())
            .build())
        .flatMap(next::exchange);
}

(For brevity, I've removed any error handling.)

The above code takes the following steps:

  1. Look up the appropriate client registration -- this contains the provider's endpoint as well as the client id and secret
  2. Construct an OAuth2PasswordGrantRequest, using the client's id and secret as the resource owner's username and password
  3. Perform the request using the WebClientReactivePasswordTokenResponseClient
  4. Set the access token as a bearer token for the request
  5. Continue to the next function in the chain

Note that to use Spring Security's OAuth 2.0 Client features, you will need to configure your app also as a client. That means at least changing your DSL to include .oauth2Client() in addition to .oauth2ResourceServer(). It will also mean configuring a ClientRegistrationRepository. To keep my comment focused on filter functions, I've left that detail out, but I'd be happy to help there, too, if necessary.

Related