4

I'm currently working on a REST 'proxy' for use in development. The idea is to launch a jar on the command-line, supplying it with a base url for the 'proxied' REST API and an existing secret key used for authentication. The proxy handles authentication/renewal and forwards all REST calls to the target API.

I thought that Spring Cloud Gateway might be a good fit for this and using request chaining have managed to get the authentication side of things working :

@Configuration
public class FilterConfiguration {

    @Bean
    public RouteLocator mock(RouteLocatorBuilder builder, @Value("${gateway.url}") String url, @Value("${secret.key}") String secretKey, MyGatewayFilterFactory filterFactory) {
        return builder.routes()
            .route("pass-through", predicateSpec -> predicateSpec
                .path("/authentication/sign/in/**", "/authentication/renew/**")
                .uri(url))
            .route("requires-auth", predicateSpec -> predicateSpec
                .alwaysTrue()
                .filters(gatewayFilterSpec -> gatewayFilterSpec
                    .filter(filterFactory.apply(new MyGatewayFilterFactory.Config(secretKey)))
                )
                .uri(url))
            .build();
    }
}
@Component
@RequiredArgsConstructor
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory .Config> {

    private final WebClient webClient;

    @Override
    public GatewayFilter apply(MyGatewayFilterFactory.Config config) {
        return (exchange, chain) -> Optional.of(AccessTokensHolder.tokens)
            .map(AtomicReference::get)
            .map(Mono::just)
            .orElseGet(() -> this.signIn(config.getSecretKey()))
            .flatMap(accessTokens -> {
                exchange.getRequest()
                    .mutate()
                    .header(HttpHeaders.AUTHORIZATION, String.format("%s %s", accessTokens.getType(), accessTokens.getAccessToken()));
                return chain.filter(exchange);
            })
            .onErrorResume(t -> Mono.empty())
            .then(Mono.fromRunnable(this.postRequest(exchange)));
    }

    private Mono<AccessTokens> signIn(String secretKey) {
        // uses the webClient to get the access tokens for the secretKey ...
    }

    private Runnable postRequest(ServerWebExchange exchange) {
        return () -> {
            ServerHttpResponse response = exchange.getResponse();
            if (HttpStatus.PRECONDITION_FAILED.equals(response.getStatusCode())) {
                
                // todo : HELP!
            }
        };
    }

    @Data
    @RequiredArgsConstructor
    public static class Config {

        public final String secretKey;
    }

    private static final class AccessTokensHolder {
        private static final AtomicReference<AccessTokens> tokens = new AtomicReference<>();
    }
}

My problem is the handling of the response. If the authenticated call responds with a 412, I need to be able to make a webClient call to refresh the access tokens and then perform the original call again (with a modified Authorization header). I can't see how to achieve this (fairly new to SCG & WebFlux)...

(Btw, if anyone knows of a better/simpler ways of achieving this, I'm all ears!)

Any help would be most appreciated!

Cheers!

ChambreNoire
  • 387
  • 7
  • 19

0 Answers0