简体   繁体   中英

Spring Cloud Gateway Filter with external configuration service call

I am working on a Spring Cloud Gateway app that has a filter controlling access to certain paths or features based on a configuration held by a different service. So if a path is associated with feature x then only allow access if the configuration service returns that feature x is enabled.

The configuration is returned as a Mono and then flatMapped to check the enabled features. This all appears to work correctly. If the feature is enabled then the request is allowed to proceed through the chain. If the feature is disabled, then the response status is set to forbidden and the request marked as complete. However, this does not appear to stop the filter chain, and the request continues to be processed and eventually returns a 200 response.

If the feature configuration is not returned from an external source and is immediately available then this logic works correctly, but this involves a blocking call and does not seem desirable. I cannot see what is wrong with the first approach. It seems to be similar to examples available elsewhere.

I believe my question is similar to this one: https://stackoverflow.com/questions/73496938/spring-cloud-api-gateway-custom-filters-with-external-api-for-authorization/75095356#75095356

Filter 1

This is the way I would like to do this:

override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        logger.info("Feature Security Filter")

        // getFeatures returns Mono<Map<String, Boolean>>
        return featureConfigService.getFeatures().flatMap { features ->
            val path = exchange.request.path.toString()
            val method = exchange.request.method.toString()

            if (featureMappings.keys.any { it.matcher(path).matches() }) {

                val pathIsRestricted = featureMappings
                        .filter { it.key.matcher(path).matches() }
                        .filter { features[it.value.requiresFeature] != true || !it.value.methodsAllowed.contains(method) }
                        .isNotEmpty()

                if (pathIsRestricted) {
                    logger.warn("Access to path [$method|$path] restricted. ")
                    exchange.response.statusCode = HttpStatus.FORBIDDEN
                    exchange.response.setComplete()
                    // processing should stop here but continues through other filters
                }
            }
            chain.filter(exchange);
        }
}

Filter 2

This way works but involves a blocking call in featureService.

override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain): Mono<Void> {
        logger.info("Feature Security Filter")

        // this call returns a Map<String, Boolean> instead of a Mono
        val features = featureService.getFeatureConfig()
        val path = exchange.request.path.toString()
        val method = exchange.request.method.toString()
        if (featureMappings.keys.any { it.matcher(path).matches() }) {

            val pathIsRestricted = featureMappings
                    .filter { it.key.matcher(path).matches() }
                    .filter { features[it.value.requiresFeature] != true || !it.value.methodsAllowed.contains(method) }
                    .isNotEmpty()

            if (pathIsRestricted) {
                logger.warn("Access to path [$method|$path] restricted. ")

                val response: ServerHttpResponse = exchange.response
                response.statusCode = HttpStatus.FORBIDDEN;
                return response.setComplete()
                // this works as this request will complete here
            }
        }
        return chain.filter(exchange)
}

When the tests run I can see that a path is correctly logged as restricted, and the response status is set to HttpStatus.FORBIDDEN as expected, but the request continues to be processed by filters later in the chain, and eventually returns a 200 response.

I've tried returning variations on Mono.error and onErrorComplete but I get the same behaviour. I am new to Spring Cloud Gateway and cannot see what I am doing wrong

After doing a few tests, I figured out that Filters are executed after route filters even if you set high order. If you need to filter requests before routing, you can use WebFilter . Here is a working Java example based on your requirements.

package com.test.test.filters;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.util.Map;

@Configuration
@Slf4j
public class TestGlobalFilter implements WebFilter, Ordered {

    private Mono<Map<String, Boolean>> test() {
        return Mono.just(Map.of("test", Boolean.TRUE));
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        log.info("Feature Security Filter");

        // getFeatures returns Mono<Map<String, Boolean>>
        return test().flatMap(features -> {

            final var isRestricted = features.get("test");

            if (Boolean.TRUE.equals(isRestricted)) {
                log.info("Feature Security stop");
                exchange.getResponse().setStatusCode(HttpStatus. FORBIDDEN);
                
                return exchange.getResponse().setComplete();
            }

            return chain.filter(exchange);
        });
    }
}


The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM