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!