1

We are using Spring Cloud Gateway with a GlobalFilter to handle token injection to the request. Here is the code for the filter:

@Override
public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
    log.info("We received a request");

    exchange.getRequest().mutate()
            .headers(h -> h.add("Authorization", String.format("Bearer %s", tokenService.getToken())));

    return chain.filter(exchange);
}

The tokenService.getToken()-method returns either a token from cache or a new one from the backend if none exists in the cache, yet. But it may come to the point that the token will be invalidated in the backend. If a request is then made with the token again, the backend responds with a 401. In this case we want to intercept the response, invalidate the cache, request a new token from the backend (which is cached again afterwards) and resend the request with the new token. Additionally a header is set indicating that the request has been retried for avoiding an infinite loop. This should happen transparently for the client.

Following a short illustration about what should be the desired data flow: enter image description here

What I've already tried so far:

@Override
public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
    log.info("We received a request");

    exchange.getRequest().mutate()
            .headers(h -> h.add("Authorization", String.format("Bearer %s", tokenService.getToken())));

    return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        final ServerHttpResponse response = exchange.getResponse();
        if (response.getStatusCode().is4xxClientError()) {
            log.error("We got a 4xx");
            if (!exchange.getRequest().getHeaders().containsKey("X-retries")) {
                log.info("Trying to resend request");
                exchange.getRequest().mutate()
                        .headers(h -> h.add("X-retries", "1"));
                tokenService.evictCache();
                exchange.getRequest().mutate()
                        .headers(h -> h.remove("Authorization"))
                        .headers(h -> h.add("Authorization", String.format("Bearer %s", tokenService.getToken())));
                chain.filter(exchange);
            } else {
                log.info("Retry failed");
            }
        }
    }));
}

But I cannot see the second request in the backend log.

How can I send the modified request again to the backend?

olivergregorius
  • 109
  • 1
  • 7

1 Answers1

0

I suppose the error is in the part of code:

 exchange.getRequest().mutate()
            .headers(h -> h.add("Authorization", String.format("Bearer %s", tokenService.getToken())));

It does not change the existed exchange, it creates a new one. So, you need save link to mutated object, like:

ServerWebExchange newExchange = exchange.getRequest().mutate()
            .headers(h -> h.add("Authorization", String.format("Bearer %s", tokenService.getToken())));

After that the newExchange must be used in your code

Timur
  • 51
  • 6
  • From the official documentation for [ServerHttpRequest.Builder mutate()](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/server/reactive/ServerHttpRequest.html#mutate--): "[...] returning either mutated values or **delegating back to this instance**." I can indeed see the added Authorization header in the request arriving at the backend service. What is not working is to receive the second request when the second `chain.filter` is executed. I assume there must be a return at this point. But thats not possible with `Mono.then()` – olivergregorius May 25 '21 at 07:53