跳至主要內容

Spring Cloud Gateway (CORS - 脚本无法获取响应主体(原因:CORS Allow Origin Not Matching Origin))

bsfc.tech大约 2 分钟架构技术CaseSpring Cloud Gateway

在Spring Cloud Gateway中处理CORS(跨源资源共享)时,可能会遇到“Vary”和“Access-Control-Allow-Origin”两个响应头重复的问题,尤其是当后端服务也配置了CORS支持时。浏览器对“Access-Control-Allow-Origin”头有唯一性要求,这意味着如果响应头中有多个“Access-Control-Allow-Origin”,请求将会被阻止。

原因分析

Spring Cloud Gateway基于Spring WebFlux,所有Web请求首先由DispatcherHandler处理,它将请求转发给具体的处理器。当请求被转发至后端服务时,后端服务也可能添加了自己的CORS配置,这就会导致响应头中出现多个“Access-Control-Allow-Origin”。

解决方案

解决这个问题有两种主要的方法:

使用DedupeResponseHeader

在Spring Cloud Gateway的配置文件中,可以使用DedupeResponseHeader来消除重复的响应头。这可以通过添加一个默认过滤器来实现:

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"
        default-filters:
          - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

这里的DedupeResponseHeader启用了DedupeResponseHeaderGatewayFilterFactory,它提供了dedupe方法来按策略处理重复的值。RETAIN_FIRST策略意味着只保留第一个值,这通常是你自己配置的值,因此能确保正确性和一致性。

自定义GlobalFilter

另一种方法是创建一个自定义的GlobalFilter,在响应发送前检查并处理重复的响应头。下面是一个示例实现:

@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {

    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            HttpHeaders headers = exchange.getResponse().getHeaders();
            headers.entrySet().stream()
                   .filter(kv -> kv.getValue().size() > 1 && (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS) || kv.getKey().equals(HttpHeaders.VARY)))
                   .forEach(kv -> {
                       if (kv.getKey().equals(HttpHeaders.VARY)) {
                           // 处理Vary头的重复
                       } else {
                           // 根据需要处理Access-Control-Allow-Origin和Access-Control-Allow-Credentials的重复
                       }
                   });
        }));
    }
}

在这个自定义的GlobalFilter中,你可以选择保留第一个、最后一个或者去重后的值,具体取决于你的需求和场景。

结论

为了避免重复的CORS响应头,推荐使用DedupeResponseHeader配置,因为它更加简洁且易于维护。但是,如果你有更复杂的需求,如不同的策略处理或额外的逻辑,自定义GlobalFilter则提供更多的灵活性。