2

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?

perkss
  • 1,037
  • 1
  • 11
  • 37
  • Can you please clarify how you will get the resource owner's username and password? Based on your explanation, it sounds like your REST API only receives a JWT in the request. – jzheaux Aug 12 '20 at 14:40
  • I will provide them at start up. So I don’t want the client whose JWT we verify to then authenticate against the service we call with the WebClient. I will authenticate with the WebClient as a OAuth client as my own application client ID. – perkss Aug 12 '20 at 14:42
  • Would the client credentials grant be more suitable, then? – jzheaux Aug 12 '20 at 14:48
  • I am required to use password grant. But the problem would be the same. As I would want to authenticate again using the client credentials. But it seems to want to use the JWT authentication. – perkss Aug 12 '20 at 14:49
  • There's something else I'm missing from the explanation so far. `oauth2ResourceServer().jwt()` will create a `JwtAuthenticationToken`, but you say that you are getting a `OAuth2TokenAuthenticationToken`. First, do you mean `OAuth2AuthenticationToken`? And second, is there any additional OAuth 2.0 Spring Security config your application is doing? – jzheaux Aug 12 '20 at 14:54
  • That was incorrectly written updated to JwtAuthenticationToken. I setup a ReactiveOAuth2AuthorizedClientManager and add it as a filter to the WebClient. Thats about it. – perkss Aug 12 '20 at 15:22
  • I will try make a toy example code base to share – perkss Aug 12 '20 at 15:22

1 Answers1

3

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.

jzheaux
  • 7,042
  • 3
  • 22
  • 36
  • Thanks very much! Had been looking at the PasswordTokenResponseClient and seemed to get it working. Will try your suggested pattern of sticking it in the filter and update. – perkss Aug 12 '20 at 15:27
  • Thanks looks good! I should get the refresh of the token for free right? – perkss Aug 12 '20 at 16:31
  • It depends on the provider: https://tools.ietf.org/html/rfc6749#section-4.3.3 – jzheaux Aug 12 '20 at 21:58