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.
- 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?
- 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?
- 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";
}