跨域是一个前后端分离开发无法避免的坑,尤其是要兼容ie。之前单项目的时候,都是在后台直接配置cors就好了,或者在nginx中配置,但是微服务要是挨个都配置,代码量大,也不是很优雅。所以我们一般都会在网关配置跨域处理,以下是我的方案,项目亲测可用。
springboot版本:2.3.9.RELEASE
@Configuration
public class CorsWebFilter implements WebFilter {
private static final String ALL = "*";
private static final String MAX_AGE = "18000L";
@Override
public Mono<Void> filter(ServerWebExchange ctx, WebFilterChain chain) {
ServerHttpRequest request = ctx.getRequest();
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(ctx);
}
String path = request.getPath().value();
ServerHttpResponse response = ctx.getResponse();
if ("/favicon.ico".equals(path)) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
HttpHeaders requestHeaders = request.getHeaders();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders().toString().replace("[", "").replace("]", ""));
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx);
}
}
一些注意事项:
1.设置ACCESS_CONTROL_ALLOW_HEADERS的时候不能把请求的数组AccessControlRequestHeaders直接给塞进去,否则ie会报错,这里需要转成String类型。
2.OPTION请求过来的时候会带着AccessControlRequestHeaders,我们在OPTION返回的时候设置跨域的请求头,下次正式请求过来的时候,就不需要设置了,因为OPTION预检验是通过的。
3.ACCESS_CONTROL_ALLOW_HEADERS设置*的话,ie上是会报错的。报错异常信息为:Access-Control-Allow-Headers 列表中不存在请求标头 x-requested-with。
4.如果发现设置的跨域响应头是重复的,那么你需要去重,代码在下方。
@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
// 指定位于 NettyWriteResponseFilter 处理完响应体后移除重复 CORS 响应头
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);
}));
}
}