I am using Spring Gateway, where I need to check further user access by Request path using DB call. My repository is like this.
public Mono<ActionMapping> getByUri(String url)
....
This is my current filter where I am using custom UsernamePasswordAuthenticationToken
implementation.
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> exchange
.getPrincipal()
.filter(principal -> principal instanceof UserAuthenticationToken) // Custom implementation of UsernamePasswordAuthenticationToken
.cast(UserAuthenticationToken.class)
.map(userAuthenticationToken -> extractAuthoritiesAndSetThatToRequest(exchange, userAuthenticationToken))
.defaultIfEmpty(exchange)
.flatMap(chain::filter);
}
private ServerWebExchange extractAuthoritiesAndSetThatToRequest(ServerWebExchange exchange, UserAuthenticationToken authentication) {
var uriActionMapping = uriActionMappingRepository.findOneByUri(exchange.getRequest().getPath().toString()).block();
if ((uriActionMapping == null) || (authentication.getPermission().containsKey(uriActionMapping.getName()))) {
ServerHttpRequest request = exchange.getRequest()
.mutate()
.header("X-Auth", authentication.getName())
.build();
return exchange.mutate().request(request).build();
}
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.setComplete();
return exchange.mutate().response(response).build();
}
However, there are several problems here, first that it is blocking call. Also I am not sure I need to mutate exchange
to return response like that. Is there anyway achieve this using filter in Spring Cloud Gateway.
Yes, it is a blocking call.
Firstly, Spring WebFlux is based on Reactor. In Reactor, most handling method will not recieve a null
from Mono
emit, e.g. map
, flatMap
. Sure, there are counterexamples, such as doOnSuccess
, see also the javadoc of Mono
.
So, we can just use handling methods to filter results instead of block
. Those handling methods will return a empty Mono
when recieve a null
value.
Secondary, when it authorize failed, we should return a empty Mono instead of calling chain.filter
. The chain.filter
means "It's OK! Just do something after the Filter!". See also RequestRateLimiterGatewayFilterFactory
, it also mutate the response
.
So, we should set response
to completed, and return a empty Mono
if authorize failed.
Try this:
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> exchange
.getPrincipal()
.filter(principal -> principal instanceof UserAuthenticationToken) // Custom implementation of UsernamePasswordAuthenticationToken
.cast(UserAuthenticationToken.class)
.flatMap(userAuthenticationToken -> extractAuthoritiesAndSetThatToRequest(exchange, userAuthenticationToken))
.switchIfEmpty(Mono.defer(() -> exchange.getResponse().setComplete().then(Mono.empty())))
.flatMap(chain::filter);
}
// Maybe return empty Mono, e.g. findOneByUri not found, or Permissions does not containing
private Mono<ServerWebExchange> extractAuthoritiesAndSetThatToRequest(ServerWebExchange exchange, UserAuthenticationToken authentication) {
return uriActionMappingRepository.findOneByUri(exchange.getRequest().getPath().toString())
.filter(it -> authentication.getPermission().containsKey(it.getName()))
.map(it -> exchange.mutate()
.request(builder -> builder.header("X-Auth", authentication.getName()))
.build());
}
About mutate request
, see also RewritePathGatewayFilterFactory
.