spring-cloud / spring-cloud-gateway

An API Gateway built on Spring Framework and Spring Boot providing routing and more.
http://cloud.spring.io
Apache License 2.0
4.53k stars 3.33k forks source link

The OPTIONS request cannot be forwarded to the service #2472

Open DawnSouther opened 2 years ago

DawnSouther commented 2 years ago

Describe the bug Because I have a service proxied by the gateway, this service needs to customize the response header of the OPTIONS request, but I found that the gateway does not send the request to my service

I found that when the preflight request passes through the gateway, the request header contains both Origin and Access-Control-Request-Method, it will enter NO_OP_HANDLER and will not access the route defined by the RouteDefinition. Does the cors of the gateway refer to intercepting the request and returning it directly? If so, is there a way to remove the cors filter of the gateway? Or can you briefly talk about why it is designed like this?

image image

Version spring-cloud-gateway-server@3.0.4

Config

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOriginPatterns: '*'
            allowedMethods: '*'
            allowedHeaders: '*'
            allowCredentials: true
      default-filters:
          - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST
rcbandit111 commented 2 years ago

Any plans to fix this soon?

rcbandit111 commented 2 years ago

@DawnSouther Can you propose some quick code fix, please? I'm blocked from this issue.

rcbandit111 commented 2 years ago

Any update on this?

rcbandit111 commented 2 years ago

@spencergibb is it possible to fix this issue soon? Unfortunately it's blocking my project.

spencergibb commented 2 years ago

PRs welcome. The earliest release will by next month.

DawnSouther commented 2 years ago

@DawnSouther Can you propose some quick code fix, please? I'm blocked from this issue.

@rcbandit111 Sorry, just saw this. There is a temporary solution, I extracted part of the logic that needs to respond to OPTIONS and put it on the gateway separately. Like this, load a custom CorsProcessor in the RoutePredicateHandlerMapping.

    @Bean
    public RoutePredicateHandlerMapping tusRoutePredicateHandlerMapping(FilteringWebHandler webHandler,
                                                                        RouteLocator routeLocator,
                                                                        GlobalCorsProperties globalCorsProperties,
                                                                        Environment environment) {
        RoutePredicateHandlerMapping routePredicateHandlerMapping = new RoutePredicateHandlerMapping(webHandler,
                routeLocator, globalCorsProperties, environment);
        routePredicateHandlerMapping.setCorsProcessor(new TusCorsProcessor());
        return routePredicateHandlerMapping;
    }
DawnSouther commented 2 years ago

PRs welcome. The earliest release will by next month.

Before formally solving this problem, I think I should listen to your ideas first, or I just need to mention a pr that ignores the interception of cors in the gateway?

rcbandit111 commented 2 years ago

@DawnSouther I want to test your temporary solution. Can you share what is the content of TusCorsProcessor?

DawnSouther commented 2 years ago

@DawnSouther I want to test your temporary solution. Can you share what is the content of TusCorsProcessor?

Of course.

public class TusCorsProcessor extends DefaultCorsProcessor {

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public boolean process(@Nullable CorsConfiguration config, ServerWebExchange exchange) {
        // 添加tus头
        String contextPath = exchange.getRequest().getURI().getRawPath();
        if (!StrUtil.endWith(contextPath, "/")) {
            contextPath = contextPath + "/";
        }
        if (antPathMatcher.match(TusConsts.TUS_PATTERN, contextPath)) {
            this.applyTusHeader(exchange.getResponse());
        }

        return super.process(config, exchange);
    }

    private void applyTusHeader(ServerHttpResponse response) {
        HttpHeaders headers = response.getHeaders();
        headers.add(TusConsts.ACCESS_CONTROL_EXPOSE_HEADER, TusConsts.ACCESS_CONTROL_EXPOSE_OPTIONS_VALUE);
        headers.add(TusConsts.TUS_RESUMABLE_HEADER, TusConsts.TUS_RESUMABLE_VALUE);
        headers.add(TusConsts.TUS_VERSION_HEADER, TusConsts.TUS_VERSION_VALUE);
        headers.add(TusConsts.TUS_MAX_SIZE_HEADER, TusConsts.TUS_MAX_SIZE.toString());
        headers.add(TusConsts.TUS_EXTENTION_HEADER, TusConsts.TUS_EXTENTION_VALUE);
        response.setStatusCode(HttpStatus.OK);
    }
}
rcbandit111 commented 2 years ago

@DawnSouther Can you share also what are the imports for this class? I can't find imports for StrUtil and TusConsts.

rcbandit111 commented 2 years ago

@DawnSouther I tried this:

    @Bean
    public RoutePredicateHandlerMapping tusRoutePredicateHandlerMapping(FilteringWebHandler webHandler,
                                                                        RouteLocator routeLocator,
                                                                        GlobalCorsProperties globalCorsProperties,
                                                                        Environment environment) {
        RoutePredicateHandlerMapping routePredicateHandlerMapping = new RoutePredicateHandlerMapping(webHandler,
                routeLocator, globalCorsProperties, environment);
        routePredicateHandlerMapping.setCorsProcessor(new CrackCorsProcessor());
        return routePredicateHandlerMapping;
    }

........

import org.springframework.lang.Nullable;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.DefaultCorsProcessor;
import org.springframework.web.server.ServerWebExchange;

public class CrackCorsProcessor extends DefaultCorsProcessor {

    @Override
    public boolean process(@Nullable CorsConfiguration config, ServerWebExchange exchange) {
        return false;
    }
}

But OPTIONS request is not forwarded again properly. @spencergibb can you also advise please how I can make a quick fix for this issue?

DawnSouther commented 2 years ago

@rcbandit111 I put the logic to be processed by options on the gateway

rcbandit111 commented 2 years ago

@DawnSouther Sorry I don't get it. Can you show me code example, please?

spencergibb commented 2 years ago

I don't have any quick fix for this. The code in your screenshots is from Spring Framework and not Gateway. It looks like we might need a custom CorsProcessor, but I'm not familiar with that bit of framework. I'll need to work with @rstoyanchev

rcbandit111 commented 2 years ago

Thanks, waiting for fix.

rstoyanchev commented 2 years ago

@spencergibb the general idea is that Spring WebFlux has built-in support for pre-flight requests, so it only finds the matching handler for the "would-be" (actual) request, then updates the response accordingly, and shortcircuits.

Keep in mind that pre-flight requests are not authenticated. This why they're handled even earlier in Spring Security, which finds a PreFlightHandler bean (implemented by DispatcherHandler) and calls it to handle pre-flight requests and then skip the rest of the filter chain which would pose a risk in case some unexpected handling took place.

To handle pre-flight requests by passing them through, you'll need some similar approach, intercept early via WebFilter, and not allow it to continue down the WebFilter chain.

DawnSouther commented 2 years ago

@rstoyanchev Thanks for your explanation. I understand your idea. It is necessary and important to consider security, but the requests proxyed by our spring gateway are not only GET, POST..., but also other types of requests (pre-flight requests). Or it can be said that the services proxied by spring gateway are not only spring-web, but also other types of services.

Maybe we can add a switch to it to control the cors filter(stragegy?) of spring gateway? Allow cors requests to be forwarded to the target service through spring gateway, fully controlled by the target service. Instead of just short-circuiting the pre-flight as it does now.

For example, I have a very practical scenario where I need to be a server that supports the tus protocol, and the tus protocol needs to respond to the options method and add headers to the response to determine the tus-version, tus-extension. . . . . . I checked a lot of places at the time, and it wasn't until the end that it was determined that the problem was caused by the spring gateway. hahaha......It's a real headache

I think spring gateway is first of all a gateway, at least to achieve forwarding, security and other functions can not affect the forwarding

lgscofield commented 2 years ago

@DawnSouther Maybe U should add this: `

org.springframework.cloud
<artifactId>spring-cloud-starter-bootstrap</artifactId>

` in U classpath, then U yml configuration file will work

alexmntmnk commented 1 year ago

Is there any solution to this problem at the moment? Or how to get around it?