tCorsProcessor 中,根据我们在appliation.yml
中的配置,给Response添加了 Vary
和 Access-Control-Allow-Origin
的头。
再接下来就是进入各个GlobalFilter进行处理了,其中NettyRoutingFilter
是负责实际将请求转发给后台微服务,并获取Response的,重点看下代码中filter的处理结果的部分:
其中以下几种header会被过滤掉的:
很明显,在图里的第3步中,如果后台服务返回的header里有 Vary
和 Access-Control-Allow-Origin
,这时由于是putAll,没有做任何去重就加进去了,必然会重复,看看DEBUG结果验证一下:
验证了前面的发现。
解决
解决的方案有两种:
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
方法可以按照给定策略处理值
private void dedupe(HttpHeaders headers, String name, Strategy strategy) {
List<String> values = headers.get(name);
if (values == null || values.size() <= 1) {
return;
}
switch (strategy) {
// 只保留第一个
case RETAIN_FIRST:
headers.set(name, values.get(0));
break;
// 保留最后一个
case RETAIN_LAST:
headers.set(name, values.get(values.size() - 1));
break;
// 去除值相同的
case RETAIN_UNIQUE:
headers.put(name, values.stream().distinct().collect(Collectors.toList()));
break;
default:
break;
}
}
- 如果请求中设置的Origin的值与我们自己设置的是同一个,例如生产环境设置的都是自己的域名xxx.com或者开发测试环境设置的都是*(浏览器中是无法设置Origin的值,设置了也不起作用,浏览器默认是当前访问地址),那么可以选用
RETAIN_UNIQUE
策略,去重后返回到前端。
- 如果请求中设置的Oringin的值与我们自己设置的不是同一个,
RETAIN_UNIQUE
策略就无法生效,比如 ”*“ 和 ”xxx.com“是两个不一样的Origin,最终还是会返回两个Access-Control-Allow-Origin
的头。此时,看代码里,response的header里,先加入的是我们自己配置的Access-Control-Allow-Origin
的值,所以,我们可以将策略设置为RETAIN_FIRST
,只保留我们自己设置的。
大多数情况下,我们想要返回的是我们自己设置的规则,所以直接使用RETAIN_FIRST
即可。实际上,DedupeResponseHeader
可以针对所有头,做重复的处理。
@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(CorsResponseHeaderFilter.class);
private static final String ANY = "*";
@Override
public int getOrder() {
// 指定此过滤器位于NettyWriteResponseFilter之后
// 即待处理完响应体后接着处理响应头
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
@SuppressWarnings("serial")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
exchange.getResponse().getHeaders().entrySet().stream()
.filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
.filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
|| kv.getKey().equals(HttpHeaders.VARY)))
.forEach(kv ->
{
// Vary只需要去重即可
if(kv.getKey().equals(HttpHeaders.VARY))
kv.setValue(kv.getValue().stream().distinct().collect(Collectors.toList()));
else{
List<String> value = new ArrayList<>();
if(kv.getValue().contains(ANY)){ //如果包含*,则取*
value.add(ANY);
kv.setValue(value);
}else{
value.add(kv.getValue().get(0)); // 否则默认取第一个
kv.setValue(value);
}
}
});
}));
}
}
此处有两个地方要注意:
1)根据下图可以看到,在取得返回值后,Filter的Order
值越大,越先处理Respon