遇到问题
此问题网上很多解决方案,其实各个都没有错,各个解决方案都是正确,但是分类成好几个类型统一引起的,把网上的中解决方案在放在一起,就会出现不但不可以解决问题,反而更加引起混乱,明明已经正确按不同方法实现了一遍,就是不行
问题分类
(1)spring mvc 单独跨域问题
(2)gateway 单独跨域问题
(3)gateway + spring mvc 后端联合跨域问题
一、spring mvc 单独跨域问题
此问题最简单,添加mvc 过滤器就可以了
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
Logger logger = LoggerFactory.getLogger("跨域过滤器");
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
// final String origin = request.getHeader("Origin");
Collection<String> aa = response.getHeaders("Access-Control-Allow-Origin");
response.setHeader("Access-Control-Allow-Origin", "*");
Collection<String> bb = response.getHeaders("Access-Control-Allow-Origin");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Authorization,authorization_refresh,content-type,_isfresh");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
// servletRequest.getRequestDispatcher("/block").forward(servletRequest, servletResponse);
return;
} else {
filterChain.doFilter(request, response);
}
}
/**
* 可以初始化Filter在web.xml里面配置的初始化参数
* filter对象只会创建一次,init方法也只会执行一次。
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("跨域过滤器初始化完成");
}
@Override
public void destroy() {
}
}
二、gateway 单独单独跨域问题
所谓单独跨域是指,网关后面的后端,不再对跨域的代码进行一次跨域设置,否则会出错,详细请看 第三节
坑
注意两个过滤器的不同
CorsWebFilter 和 GlobalFilter , 网上有针对这两种过滤器的【跨域设置代码】,其中亲自尝试GlobalFilter完全没有效果,具体原因是
由于gateway使用的是webflux,而不是springmvc,需要覆盖掉webflux默认的CORS处理配置,通过注册新的CorsWebFilter Bean来解决跨域问题,而且似乎 GlobalFilter 是在 CorsWebFilter 之后执行的,所以
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
完全无效,系统进不到过滤器GlobalFilter已经被CorsWebFilter拦截了
具体步骤是在post,get 之前,前端浏览器会预先向发起options 请求,预先访问服务的是否许可,当服务端回复可以访问后,再提交post 或 get进行第二次访问
因此要用 CorsWebFilter 作为过滤器首选,代码如下
@Configuration
public class GwCorsFilter {
@Bean
public CorsWebFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
//cors跨域配置对象
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(false);//是否允许携带cookie
// corsConfiguration.addAllowedOriginPattern("*");
corsConfiguration.addAllowedOrigin("*"); //允许跨域访问的域名,可填写具体域名,*代表允许所有访问
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
/*
corsConfiguration.addAllowedMethod(HttpMethod.POST);//允许访问类型:get post 等,*代表所有类型
corsConfiguration.addAllowedMethod(HttpMethod.GET);
corsConfiguration.addAllowedMethod(HttpMethod.HEAD);
corsConfiguration.addAllowedMethod(HttpMethod.OPTIONS);
corsConfiguration.addAllowedMethod(HttpMethod.PATCH);
*/
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
坑2
如果选择
corsConfiguration.setAllowCredentials(true);
参数会出现一下错误
ava.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
at org.springframework.web.cors.CorsConfiguration.validateAllowCredentials(CorsConfiguration.java:473)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ com.alibaba.csp.sentinel.adapter.spring.webflux.SentinelWebFluxFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HTTP OPTIONS "/chrisboot/api/v1/NewsLayout/Search_index_VwData?type=1" [ExceptionHandlingWebHandler]
顾名思义,修改以下代码可执行
corsConfiguration.setAllowCredentials(true);//是否允许携带cookie
corsConfiguration.addAllowedOriginPattern("*");
//corsConfiguration.addAllowedOrigin("*");
具体不同是 返回前端的参数不同,如图
或者还有其他用途,未进一步尝试,不是主要矛盾,至少我的前端是不需要携带cookie的
三、Spring Cloud Gateway 、 Spring MVC 联合跨域
此问题最坑,耗费大量时间
单独gateway ,单独 web mvc都没问题,两个框架联合起来, 网关 + web 后端 就有问题了!!!
主要问题体现,前端浏览器返回
' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
具体原因是因为 经过了 网关 + 后端web 两层过滤 , 所有参数都变成了2次重复,而 Access-Control-Allow-Origin 只允许一个,造成了错误
既然知道原因了,就寻找解决方案,思路如下
解决方案
2个过滤去掉其中一个
(1)方案一
去掉 gateway ,保留web mvc过滤器 , 直接网关都进不去了,何况到后端,这种方式pass
(2) 方案二
测试 可行,验证通过
但是存在一定局限性 , 你只能控制网关, 以后 所有的后端不能再加跨域控制, 如果所有程序是你开发的可以,如果是别人的后端程序,你不一定能控制到代码。
其二后端程序也有一定需求是要独立运行的,不是全通过网关,这样后端程序不加跨域控制器,也无法独立运行,也不可能要求作者维护两套不同版本。
方案二只能说可以暂时解决问题,但是不完美
(3)方案三
经过测试,gateway + web mvc后端 两个都加过滤器的话,其实是能回到response返回给前端的,只是前端浏览器接收失败的(意思是两个过滤器都执行了,同步加了两套header), 那这样就再动态等它加完两套加完后,再代码删除就好了。
实施情况1: 删除后端web mvc 情况,先判断 gateway 没有把header传过来,又传过来,就不添加了,或者添加后,执行完过滤器后,再动态把多余的header删除掉
结果:失败, 因为搞不定在后端侧,读取 gateway 读出的 header ,或者有方法,但是我没找到
实施情况2: gateway 过滤器 GlobalFilter 分为前过滤器,后过滤器,再执行完后端后,查找有没有多余的header ,发现有,删除掉
结果:成功 , 问题彻底解决,代码如下
@Component
public class GlobalCustomFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//前置过滤
return chain.filter(exchange)
.then(Mono.fromRunnable(
() -> {
//后置过滤
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers_after = response.getHeaders();
if (headers_after.get("Access-Control-Allow-Origin")!=null && headers_after.get("Access-Control-Allow-Origin").size() > 1)
headers_after.set("Access-Control-Allow-Origin", "*");
}
));
//return chain.filter(exchange);
}
}
问题彻底解决,只修改网关灵活方便, 后端程序可以保持自己的跨域过滤器
注意 GlobalFilter CorsWebFilter 两个联合使用,一个做后期去掉多余header , 一个作为前期加入第一个header (第二个又后端的mvc再加)