10

I have an Spring + CXF application which consumes a Transmission API: Transmission RPC running in another server.

According to Transmission docs, you need to send a token which is generated on the first request. The server then responds with a 409 http code along with a header containing the token. This token should be sent on all subsequent calls:

2.3.1. CSRF Protection Most Transmission RPC servers require a X-Transmission-Session-Id header to be sent with requests, to prevent CSRF attacks. When your request has the wrong id -- such as when you send your first request, or when the server expires the CSRF token -- the Transmission RPC server will return an HTTP 409 error with the right X-Transmission-Session-Id in its own headers. So, the correct way to handle a 409 response is to update your X-Transmission-Session-Id and to resend the previous request.

I was looking for solution either using a CXF filter or interceptor, that basically will handle the 409 response and retry the initial request adding the token header. I'm thinking that clients can persist this token and send it in future calls.

I'm not very familiar with cxf so I was wondering if this can be accomplish and how. Any hint would be helpful.

Thanks!

David Morabito
  • 1,498
  • 1
  • 12
  • 22

2 Answers2

2

Here spring-retry can be utilized which is now an independent project and no longer part of spring-batch.

As explained here retry callback will help make another call updated with the token header.

Pseudo code / logic in this case would look something like below

RetryTemplate template = new RetryTemplate();
Foo foo = template.execute(new RetryCallback<Foo>() {
    public Foo doWithRetry(RetryContext context) {
        /* 
         * 1. Check if RetryContext contains the token via hasAttribute. If available set the header else proceed
         * 2. Call the transmission API 
         * 3.a. If API responds with 409, read the token 
         *    3.a.1. Store the token in RetryContext via setAttribute method
         *    3.a.2. Throw a custom exception so that retry kicks in
         * 3.b. If API response is non 409 handle according to business logic
         * 4. Return result
         */
    }
});

Make sure to configure the RetryTemplate with reasonable retry & backoff policies so as to avoid any resource contention / surprises.

Let know in comments in case of any queries / roadblock.

N.B.: RetryContext's implementation RetryContextSupport has the hasAttribute & setAttribute method inherited from Spring core AttributeAccessor

Bond - Java Bond
  • 3,972
  • 6
  • 36
  • 59
1

Assuming you are using Apache CXF JAX RS Client it is easy to do by just creating a custom Runtime Exception and ResponseExceptionMapper for it. So the idea is to manually convert 409 outcomes to some exception and then handle them correctly (in your case retry the service call).

See following code snipped for fully working example.

@SpringBootApplication
@EnableJaxRsProxyClient
public class SpringBootClientApplication {
    // This can e stored somewhere in db or elsewhere 
    private static String lastToken = "";

    public static void main(String[] args) {
        SpringApplication.run(SpringBootClientApplication.class, args);
    }

    @Bean
    CommandLineRunner initWebClientRunner(final TransmissionService service) {
        return new CommandLineRunner() {
            @Override
            public void run(String... runArgs) throws Exception {
                try {
                    System.out.println(service.sayHello(1, lastToken));
                // catch the TokenExpiredException get the new token and retry
                } catch (TokenExpiredException ex) {
                    lastToken = ex.getNewToken();
                    System.out.println(service.sayHello(1, lastToken));
                }
             }
        };
    }

    public static class TokenExpiredException extends RuntimeException {
        private String newToken;

        public TokenExpiredException(String token) {
            newToken = token;
        }

        public String getNewToken() {
            return newToken;
        }
     }

     /**
      * This is where the magic is done !!!!
     */
     @Provider
     public static class TokenExpiredExceptionMapper implements ResponseExceptionMapper<TokenExpiredException> {

        @Override
        public TokenExpiredException fromResponse(Response r) {
            if (r.getStatus() == 409) {
                return new TokenExpiredException(r.getHeaderString("X-Transmission-Session-Id"));
            }
            return null;
        }

    }

    @Path("/post")
    public interface TransmissionService {
        @GET
        @Path("/{a}")
        @Produces(MediaType.APPLICATION_JSON_VALUE)
        String sayHello(@PathParam("a") Integer a, @HeaderParam("X-Transmission-Session-Id") String sessionId)
            throws TokenExpiredException;
    }
}
Babl
  • 7,446
  • 26
  • 37