Spring Cloud Gateway 2.x 跨域出现“Multiple CORS header”的问题解决

最近在对公司的API网关进行升级,从Zuul Gateway切换到最新的Spring Cloud Gateway。整个过程升级比较顺利,可以做到无感升级,唯一的在跨域请求设置方面,遇到了一个问题,即提示

Multiple CORS header ‘Access-Control-Allow-Origin’ not allowed

最终导致跨域请求失败。该错误已经提示得很明白了,意思是不允许多个跨越请求头“Access-Control-Allow-Origin”,错误现象如下:
在这里插入图片描述重复的跨域请求头
错误原因分析
跟踪了大半天,最终定位到问题所在,算是Spring Cloud Gateway的一个Bug,出问题的过滤器是NettyRoutingFilter这个过滤器,该过滤器位于Gateway过滤器链条的倒数第二位,看看问题出在哪里:
在这里插入图片描述
问题解决
既然问题出在过滤器链条上,那么还是用Spring的方式,增加一个过滤器,插入到过滤器链条中。不过,新增的这个过滤器在整个链条上的位置有特殊要求。
当请求经过NettyRoutingFilter处理后,并不会马上响应客户端请求,接下来还有重要的一步要做,那就是处理响应体(ResponseBoby),由NettyWriteResponseFilter这个过滤器来处理,所以,要修复这个问题,就在处理完响应体之后立马再处理重复的跨域请求头就OK了,具体代码如下:

  • 跨域请求头重复处理过滤器:CorsResponseHeaderFilter.java
package com.einwin.platform.edge.filter;

import java.util.ArrayList;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;

/**
 * 跨域请求头处理过滤器扩展
 * @Author 
 * @Create 2019-04-22 14:20:06
 */
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
	@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.defer(() -> {
			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)))
					.forEach(kv -> 
			{
				kv.setValue(new ArrayList<String>() {{add(kv.getValue().get(0));}});
			});
			
			return chain.filter(exchange);
		}));
	}
}
  • 跨域全局配置:CorsConfiguration.java
package com.einwin.platform.edge.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.DefaultCorsProcessor;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPatternParser;

import com.einwin.platform.edge.filter.CorsResponseHeaderFilter;
import com.einwin.platform.sso.common.RequestHeaderKeys;

/**
 * 网关跨域配置
 * @Author 
 * @Create 2019-04-19 15:29:54
 */
@Configuration
public class CorsConfiguration {
	@Bean
	public CorsResponseHeaderFilter corsResponseHeaderFilter() {
		return new CorsResponseHeaderFilter();
	}
	
	@Bean
	public CorsWebFilter corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
		source.registerCorsConfiguration("/**", buildCorsConfiguration());
		
		CorsWebFilter corsWebFilter = new CorsWebFilter(source, new DefaultCorsProcessor() {
			@Override
			protected boolean handleInternal(ServerWebExchange exchange, CorsConfiguration config, 
				boolean preFlightRequest) 
			{
				// 预留扩展点
				// if (exchange.getRequest().getMethod() == HttpMethod.OPTIONS) {
					return super.handleInternal(exchange, config, preFlightRequest);
				// }

				// return true;
			}
		});
		
		return corsWebFilter;
	}
	
	private CorsConfiguration buildCorsConfiguration() {
		CorsConfiguration corsConfiguration = new CorsConfiguration();
		corsConfiguration.addAllowedOrigin("*");
		
		corsConfiguration.addAllowedMethod(HttpMethod.OPTIONS);
		corsConfiguration.addAllowedMethod(HttpMethod.POST);
		corsConfiguration.addAllowedMethod(HttpMethod.GET);
		corsConfiguration.addAllowedMethod(HttpMethod.PUT);
		corsConfiguration.addAllowedMethod(HttpMethod.DELETE);
		corsConfiguration.addAllowedMethod(HttpMethod.PATCH);
		// corsConfiguration.addAllowedMethod("*");
		
		corsConfiguration.addAllowedHeader("origin");
		corsConfiguration.addAllowedHeader("content-type");
		corsConfiguration.addAllowedHeader("accept");
		corsConfiguration.addAllowedHeader("x-requested-with");
		corsConfiguration.addAllowedHeader("Referer");
		corsConfiguration.addAllowedHeader(RequestHeaderKeys.USER_AGENT);
		corsConfiguration.addAllowedHeader(RequestHeaderKeys.TOKEN);
		corsConfiguration.addAllowedHeader(RequestHeaderKeys.REFRESH_TOKEN);
		corsConfiguration.addAllowedHeader(RequestHeaderKeys.OS);
		corsConfiguration.addAllowedHeader(RequestHeaderKeys.X_APP_KEY);
		corsConfiguration.addAllowedHeader(RequestHeaderKeys.X_DEVICE_ID);
		corsConfiguration.addAllowedHeader(RequestHeaderKeys.X_TOKEN);
		// corsConfiguration.addAllowedHeader("*");
		
		corsConfiguration.setMaxAge(7200L);
		corsConfiguration.setAllowCredentials(true);
		return corsConfiguration;
	}
}

OK,问题完美解决!

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 34
    评论
对于Spring Cloud Gateway跨域问题,你可以通过以下步骤解决: 1. 在你的Spring Cloud Gateway项目中,创建一个全局的跨域配置类,比如命名为CorsConfig。 2. 在CorsConfig类上添加注解`@Configuration`,使其成为一个配置类。 3. 在CorsConfig类中添加一个方法来配置跨域规则,比如命名为addCorsMappings。 4. 在addCorsMappings方法上添加注解`@Bean`,使其成为一个Bean。 5. 在addCorsMappings方法中使用CorsRegistry对象来配置跨域规则。例如: ```java @Configuration public class CorsConfig { @Bean public WebFluxConfigurer corsConfigurer() { return new WebFluxConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") .exposedHeaders("Authorization") .allowCredentials(true) .maxAge(3600); } }; } } ``` 在上述示例中,通过调用CorsRegistry对象的addMapping方法来添加跨域规则。其中,allowedOrigins设置允许的源(域)地址,allowedMethods设置允许的HTTP方法,allowedHeaders设置允许的请求头,exposedHeaders设置允许暴露的响应头,allowCredentials设置是否允许发送cookie信息,maxAge设置预检请求的缓存时间。 这样配置后,Spring Cloud Gateway就会支持跨域请求了。注意,如果你使用的是WebFlux框架,需要返回WebFluxConfigurer对象并重写addCorsMappings方法;如果使用的是WebMvc框架,则需要返回WebMvcConfigurer对象并重写addCorsMappings方法。 希望以上信息对你有所帮助!如有更多问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值