Mixed up access token in Spring WebMVC app using Spring Security oAuthClient

250 Views Asked by At

I am using spring Spring Boot / Security 5.4.8 with oAuth2-Client.

My App has background processing (threadpool with 3 parallel jobs). Part of each job is consuming remote ReST service

  • which requires Jwt-Authentication
  • depending on the tenant context of the job a different scope is necessary (if job is processed for tenant A, scope must be A and so on...)

In a trivial scenario only jobs of one tenant are processed and the access token can be reused all the time (until refreshing). This works fine.

But with jobs of different tenants, the access token has to be changed. Unfortunatelly access token get mixed up. Means the request of Job 1 (Tenant A) contains AT of Tenant B and so on. I haven't observed any deterministic behaviour when access token mapping works and when it get mixed up.

My striped down code looks as following.

Do you have any suggestion how to approach this problem? Or am I doing any conceptional failures?

@Configuration
public class CommonConfig {

    @Bean
    OAuth2AuthorizedClientManager defaultAuthClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {

        OAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
        var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
            clientRegistrationRepository, oAuth2AuthorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        authorizedClientManager.setAuthorizationSuccessHandler((authorizedClient, principal, attributes) ->
            oAuth2AuthorizedClientService.saveAuthorizedClient(authorizedClient, principal));
        authorizedClientManager.setAuthorizationFailureHandler(
            new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
                (clientRegistrationId, principal, attributes) ->
                    oAuth2AuthorizedClientService.removeAuthorizedClient(clientRegistrationId, principal.getName())));
        return authorizedClientManager;
    }

    @Bean(name = "threadPoolTaskExecutor")
    public TaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.initialize();
        return executor;
    }

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
        final List<ClientRegistration> registrations = new ArrayList<>(OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());
        final ClientRegistrationRepository parent = new InMemoryClientRegistrationRepository(registrations);

    return (registrationId) -> {
        final ClientRegistration clientRegistration = parent.findByRegistrationId("TEMPLATE");
        if (clientRegistration == null) {
            return null;
        }

        return ClientRegistration.withClientRegistration(clientRegistration)
                .registrationId(registrationId)
                .scope(resultingScopes)
                .build();
    };
}


@Component
public class JobScheduler {
    
    @Scheduled(cron = "0/10 * * * * *")
    public void trigger() {
        List<Job> jobs = this.mockService.getJobs();
        jobs.forEach(job -> this.jobExecutor.process(job));
    }
}

@Component
public class JobExecutor {

    @Async("threadPoolTaskExecutor")
    public CompletableFuture<JobState> processJob(Job job) {
        try{
            SecurityContextHolder.getContext().setAuthentication(job.getAuth());
            this.myService.getMyRemoteServiceResponse(job.getId());
        } finally {
            SecurityContextHolder.getContext().setAuthentication(null);
        }
    }

}

@Service
public class MyService {
    public MyService(WebClient.Builder webClientBuilder) {
        ...
    }

    private WebClient getWebClient(String scope) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
            authorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId(scope);

        return webClientBuilder
            .apply(oauth2Client.oauth2Configuration())
            .build();
    }

    public String getMyRemoteServiceResponse(long id) {
        String tenantName = SecurityContextHolder.getContext().getAuthentication().getName();
        return this.getWebClient(tenantName)
            .method(httpMethod)
            .uri(uri)
            .attributes(oauth2AuthorizedClient(this.getAuthorizedClient(this.getAuthenticationName())))
            .retrieve()
            .bodyToMono(String.class)
            .block();
    }

}

0

There are 0 best solutions below