reidarok reidarok - 25 days ago 11
Java Question

Using OAuth2RestTemplate on behalf of a number of users

I'm creating a system which regularly exports data on behalf of many users to an external system, with OAuth2-authenticated HTTP requests.

I have successfully been able to communicate with the external service using Spring Security OAuth2, with an OAuth2RestTemplate configured like this:

@Configuration
@EnableOAuth2Client
public class ExternalServiceConfiguration {

@Autowired
private OAuth2ClientContext oauth2Context;

@Bean
public OAuth2ProtectedResourceDetails credentials() {

ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setAccessTokenUri("https://external-service.example.com/OAuth/Token");
details.setClientId("abcdefghijklmnopq");
details.setClientSecret("123456789123456789123456789");
details.setGrantType("client_credentials");

return details;
}

@Bean
public OAuth2RestTemplate externalServiceRestTemplate() {
return new OAuth2RestTemplate(credentials(), oauth2Context);
}
}


This works well, and I'm able to inject the OAuth2RestTemplate bean into my services:

@Autowired
@Qualifier("externalServiceRestTemplate")
private OAuth2RestTemplate restTemplate;


However, in my application, I have a number of users that each need to configure their own client keys. In case it's relevant, I'm doing this a a batch job, meaning it is done outside of a regular HTTP request, and sometimes in the same thread context.

This means that I will need to have multiple OAuth2ProtectedResourceDetails, and, I assume, multiple OAuth2RestTemplate instances as well. As this is something each user will be configuring on their own, it has to happen dynamically, based on client credentials saved in a database.

Does anyone have any advice on how to configure a dynamic number of OAuth2RestTemplate instances in an efficient, but thread-safe way?

Answer

As nobody has replied yet, I'll attempt to answer my own question.

I have created a Repository bean which caches and returns a RestTemplate based on a secret client key:

@Repository
public class ExternalServiceRepository {

    private static ConcurrentHashMap<String, OAuth2RestTemplate> restTemplates = new ConcurrentHashMap<>();

    /**
     * Get a RestTemplate for a specific client based on it's client secret id.
     * Create one if it hasn't been initialized yet.
     */
    public OAuth2RestTemplate restTemplate(String clientKey) {

        synchronized (restTemplates) {
            OAuth2RestTemplate restTemplate = restTemplates.get(clientKey);

            if (restTemplate == null) {
                ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
                details.setAccessTokenUri("https://external-service.example.com/OAuth/Token");
                details.setClientId("abcdefghijklmnopq");
                details.setClientSecret(clientKey);
                details.setGrantType("client_credentials");

                restTemplate = new OAuth2RestTemplate(details, new DefaultOAuth2ClientContext());
                restTemplates.put(clientKey, restTemplate);
            }

            return restTemplate;
        }
    }
}

Instead of using the @EnableOAuth2Client annotation, which sets up an OAuth2 client context for every HTTP session, I'm creating my own DefaultOAuth2ClientContext. As I'm only using client credentials, I believe this code to be thread safe (please prove me wrong if you think otherwise).

Finally, instead of injecting the RestTemplate, I'm injecting and using my repository to get access to the restTemplate for a given client secret:

RestTemplate restTemplate =
    externalServiceRepository.restTemplate("123456789123456789123456789");
Comments