Search code examples
springspring-bootretrofitretrofit2

Reuse existing token rather than requesting it on every request in spring boot + Retrofit app


I have a spring boot application that uses Retrofit to make requests to a secured server.

My endpoints:

public interface ServiceAPI {

    @GET("/v1/isrcResource/{isrc}/summary")
    Call<ResourceSummary> getResourceSummaryByIsrc(@Path("isrc") String isrc);

}

public interface TokenServiceAPI {

    @FormUrlEncoded
    @POST("/bbcb6b2f-8c7c-4e24-86e4-6c36fed00b78/oauth2/v2.0/token")
    Call<Token> obtainToken(@Field("client_id") String clientId,
                            @Field("scope") String scope,
                            @Field("client_secret") String clientSecret,
                            @Field("grant_type") String grantType);

}

Configuration class:

@Bean
Retrofit tokenAPIFactory(@Value("${some.token.url}") String tokenUrl) {
    Retrofit.Builder builder = new Retrofit.Builder()
            .baseUrl(tokenUrl)
            .addConverterFactory(JacksonConverterFactory.create());
    return builder.build();
}

@Bean
Retrofit serviceAPIFactory(@Value("${some.service.url}") String serviceUrl, TokenServiceAPI tokenAPI) {
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(new ServiceInterceptor(clientId, scope, clientSecret, grantType, apiKey, tokenAPI))
            .build();

    Retrofit.Builder builder = new Retrofit.Builder()
            .baseUrl(repertoireUrl)
            .client(okHttpClient)
            .addConverterFactory(JacksonConverterFactory.create());
    return builder.build();
}

Interceptor to add the Authorization header to every request

public class ServiceInterceptor implements Interceptor {

    public ServiceInterceptor(String clientId,
                                        String scope,
                                        String clientSecret,
                                        String grantType,
                                        String apiKey,
                                        TokenServiceAPI tokenAPI) {
        this.clientId = clientId;
        this.scope = scope;
        this.clientSecret = clientSecret;
        this.grantType = grantType;
        this.apiKey = apiKey;
        this.tokenAPI = tokenAPI;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request newRequest = chain.request().newBuilder()
                .addHeader(AUTHORIZATION_HEADER, getToken())
                .addHeader(API_KEY_HEADER, this.apiKey)
                .build();
        return chain.proceed(newRequest);
    }

    private String getToken() throws IOException {
        retrofit2.Response<Token> tokenResponse = repertoireTokenAPI.obtainToken(clientId, scope, clientSecret, grantType).execute();
        String accessToken = "Bearer " + tokenAPI.body().getAccessToken();
        return accessToken;
    }

}

This is working as expected, the problem is that the token is being requested for every request rather than using the existing valid one. How can one store the token somewhere and re-use it? I was wondering if Retrofit had a built-in solution.


Solution

  • a possible option with caching:

    add caffeiene

    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
    

    add @Cacheable("your-token-cache-name") on the method returning the token, looks like getToken above

    add max cache size and expiration configuration in application.yml e.g. 500 entries and 10 minutes for configuration below

    spring.cache.cache-names=your-token-cache-name
    spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
    

    example from: https://www.javadevjournal.com/spring-boot/spring-boot-with-caffeine-cache/