2

I am using Retrofit 2.3.0 to talk to an API that uses JWT for authentication from a Spring Boot application.

To make it work, I created an Interceptor implementation:

private static class JwtAuthenticationInterceptor implements Interceptor {
    private Supplier<String> jwtTokenSupplier;

    private JwtAuthenticationInterceptor(Supplier<String> jwtTokenSupplier) {
        this.jwtTokenSupplier = jwtTokenSupplier;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();

        Request.Builder builder = original.newBuilder()
                                          .header("Authorization",
                                                  String.format("Bearer %s", jwtTokenSupplier.get()));

        Request request = builder.build();
        return chain.proceed(request);
    }
}

In my Spring service, I let Retrofit create an instance of the API interface in the constructor:

public MySringServiceImpl() {
    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(createLoggingInterceptor())
            .addInterceptor(new JwtAuthenticationInterceptor(this::createJwtToken))
            .build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://my.remoteapi.com/api/")
            .addConverterFactory(JacksonConverterFactory.create())
            .client(client)
            .build();

    api = retrofit.create(MyRemoteApi.class);
}

So in the actual methods of my service, I use something like this:

public List<Stuff> getStuffFromApi() {
    try {
        List<Stuff> response = api.getStuff().execute().body();
        if (response != null) {
            return response;
        } else {
            return new ArrayList<>();
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

The createJwtToken method create JWT token (Using the Java JWT library)

private String createJwtToken() {
    return Jwts.builder()
               .setIssuer("http://my.remoteapi.com/api/")
               .setId("my-test-id")
               .setIssuedAt(new Date())
               .setExpiration(new Date(ZonedDateTime.now().plusSeconds(60).toEpochSecond() * 1000))
               .claim("uid", "123")
               .signWith(SignatureAlgorithm.HS512,
                         "my-very-secret-key"
                                 .getBytes())
               .compact();
}

The actual problem:

The uid claim needs to contain the id of the current user (instead of being hardcoded like it is now). I am well aware on how to get the Spring principal in the RestController and than pass that down to the service, but how would I instruct the interceptor to use the id of that principal for the call that is happening? Should I create a new Retrofit instance for each call or are there better ways to handle this?

Wim Deblauwe
  • 25,113
  • 20
  • 133
  • 211
  • There is a not so related issue, not about your question, but the code you posted. Instead of generating a new token each time, it's better to use a cache to manage the tokens. I suggest using [guava's local cache][1] to do this. If your application has multi instances, you can also use a distributed cache like memcache or redis to achieve this. [1]: https://github.com/google/guava/wiki/CachesExplained "cache in guava" – a.l. Oct 31 '17 at 03:48

0 Answers0