Search code examples
javaspring-bootspring-securityspring-security-oauth2

How to perform a refresh with spring-boot-starter-oauth2-client


I'm using spring-boot-starter-oauth2-client to authenticate my user with Google. This works well and I can sign in and get valid access and refresh token as expected.

I'm creating the access token as such:

public class TokenServiceImpl implements TokenService {

    private final OAuth2AuthorizedClientService clientService;

    @Override
    public GoogleCredentials credentials() {
        final var accessToken = getAccessToken();

        return getGoogleCredentials(accessToken);
    }

    private GoogleCredentials getGoogleCredentials(String accessToken) {

        return GoogleCredentials
                .newBuilder()
                .setAccessToken(new AccessToken(accessToken, null))
                .build();
    }

    private String getAccessToken() {
        final var oauthToken = (OAuth2AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();

        return clientService.loadAuthorizedClient(
                oauthToken.getAuthorizedClientRegistrationId(),
                oauthToken.getName()).getAccessToken().getTokenValue();
    }
}

The token is ultimately being used in the Google Photo API client as such

    private PhotosLibraryClient getClient() {
        
        final var settings =
                PhotosLibrarySettings
                        .newBuilder()
                        .setCredentialsProvider(FixedCredentialsProvider.create(tokenService.credentials()))
                        .build();

        return PhotosLibraryClient.initialize(settings);
    }

The problem is that the token will expire after a short period and I'd like to refresh it to keep it active.

I'm unsure what pattern of methods I can use to do this, without having to write the entire OAuth flow (defeating the purpose of something like the Spring oauth2-client).

So far I have no other token/security/filter logic in my application.

Do I just need to write it all out manually, or is there another way I can do this?


Solution

  • The OAuth2AuthorizedClientManager will take care of refreshing your access token for you, assuming you get a refresh token along with your access token. The doco for OAuth2AuthorizedClientManager is at

    https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2client

    When configuring your OAuth2AuthorizedClientManager, make sure you have included refreshToken in the OAuth2AuthorizedClientProvider...

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {
    
        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .authorizationCode()
                        .refreshToken()
                        .build();
    
        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
    
        // Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
        // map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
        authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
    
        return authorizedClientManager;
    }
    

    You then use the OAuth2AuthorizedClientManager to get the access token. The sample from the spring doco is below...

    @Controller
    public class OAuth2ClientController {
    
        @Autowired
        private OAuth2AuthorizedClientManager authorizedClientManager;
    
        @GetMapping("/")
        public String index(Authentication authentication,
                            HttpServletRequest servletRequest,
                            HttpServletResponse servletResponse) {
    
            OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
                    .principal(authentication)
                    .attributes(attrs -> {
                        attrs.put(HttpServletRequest.class.getName(), servletRequest);
                        attrs.put(HttpServletResponse.class.getName(), servletResponse);
                    })
                    .build();
            OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
    
            OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
    
            ...
    
            return "index";
        }
    }
    

    If the current accessToken has expired, this will automatically request a new accessToken using the previously obtained refreshToken.