How to use Spring Security 5 and OAuth2 Client to get refresh tokens and make API calls?

I'm currently building a Spring Boot App with Spring Security + OAUth2 protocol.

Here is the Authorization Guide from Spotify I'm following

I'm having trouble understanding how to do steps 2 - 4 of Authorization Code Flow. I was able to get authorization and get a authorization code to exchange for a access and refresh token, but I'm not sure how to get the tokens and then start making API calls.

Reading the Spring documentation got me confused about certain things.

  1. How do I obtain the token? I notice its stored in the URL of my redirect after I login, do I get it using a query parameter or is it stored in an OAuth2ClientService object?
  2. The Authorization Guide states I have to make a POST call to the token endpoint to get the refresh and access token. I assume I'm not doing this with WebClient/RestTemplate since I was able to do a GET request for login using the application properties. If so how do I accomplish this?
  3. How can I then use these tokens to get access to API data? Normally I would use WebClient to make REST API calls if a token wasn't necessary. If I get a token do I proceed how I would normally but with an access token as my query.

Here is my application.properties

#
# OAuth ClientRegistration Properties
#
spring.security.oauth2.client.registration.spotify.client-id=#
spring.security.oauth2.client.registration.spotify.client-secret=#
spring.security.oauth2.client.registration.spotify.provider=spotify-provider
spring.security.oauth2.client.registration.spotify.client-authentication-method=basic
spring.security.oauth2.client.registration.spotify.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.spotify.redirect-uri=http://localhost:8080/redirect
spring.security.oauth2.client.registration.spotify.scope=user-read-private,user-read-email

#
# OAuth ProviderDetails Properties
#
spring.security.oauth2.client.provider.spotify-provider.authorization-            
uri=https://accounts.spotify.com/authorize?show_dialog=true

spring.security.oauth2.client.provider.spotify-provider.token-  
uri=https://accounts.spotify.com/api/token

spring.security.oauth2.client.provider.spotify-provider.user-info-uri=https://api.spotify.com/v1/me
spring.security.oauth2.client.provider.spotify-provider.user-name-attribute=id

Here is my WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests()
        .antMatchers("/redirect")
        .permitAll()

        .and()

        .authorizeRequests()
        .anyRequest().authenticated()

        .and()

        .oauth2Login()
        .loginPage("/login")
        .permitAll();
     }
 }

Controller

@Controller
public class HomeController {

@Autowired
private OAuth2AuthorizedClientService authorizedClientService;

@GetMapping("/login")
public String getLogin()
{
    return "login";
}

///login/oauth2/code/spotify

@GetMapping("/redirect")
public String getRedirect()
{
    return "redirect";
}

@GetMapping("/home")
public String getHome()
{       
    return "home";
}
}

I'm still a beginner at this, and it's taking me a while to understand so I thank you in advanced for the help.


Solution 1:

Got it to work. Apparently I was supposed to integrate WebClient with an ExchangeFilterFunction that makes use of the OAuth2AuthorizedClientManager which handles the authorization code exchange for access token and refresh token. I followed and read the documentation until I understood it. Here's the section that helped me the most.

Here are the changes I made to my code...

I added a new config class to integrate the webclient with a exchangefilterfunction.

@Configuration
public class WebClientConfig {

@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) {
    
    OAuth2AuthorizedClientProvider authorizedClientProvider = 
            OAuth2AuthorizedClientProviderBuilder.builder()
            .authorizationCode()
            .refreshToken()
            .build();
    
    DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                    clientRegistrationRepository, authorizedClientRepository);
   
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
}


@Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = 
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    
    oauth2Client.setDefaultClientRegistrationId("spotify");
    
    return WebClient.builder()
            .apply(oauth2Client.oauth2Configuration())
            .build();
}
}

Then I just used the WebClient how I would regularly without doing OAuth2 in my controller:

@GetMapping("/redirect")
public String getRedirect()
{   
    String resourceUri = "https://api.spotify.com/v1/me/top/artists";
    
    String body = webClient
            .get()
            .uri(resourceUri)
            .retrieve()
            .bodyToMono(String.class)
            .block();
    
    System.out.println(body);
    
    return "redirect";
}