跨域问题对于后端来说已经不是什么新鲜的事,传统gataway跨域网上有很多解决办法和实现方案比如
1.在yml中添加
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allow-credentials: true
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
2.在代码中添加
3.nginx 配置 添加请求头解决跨域
我遇到的问题:
nginx解决跨域是可以的,但是使用方法1和2 对WebFlux 都不工作,webflux使用的是先经过WebFilter而不是GlobalFilter,我们知道跨域解决就是response header 添加Access-Control-Allow-Origin等一些控制参数 所以参考方法2
第一版WebFlux实现方法
添加 CorsFilter (可用下文中的CorsWebFilterConfiguration 代替)
package web.filter.cors;
import com.baozun.bs.moncler.common.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Optional;
@Slf4j
@Component
class CorsFilter implements WebFilter, Ordered {
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
log.info("CorsFilter filter ");
ServerHttpRequest request = exchange.getRequest();
HttpHeaders reqHeaders = request.getHeaders();
URI uri = request.getURI();
log.info("CorsFilter filter request.getMethod():{},uri:{}", request.getMethod(), uri);
if (request.getMethod() == HttpMethod.OPTIONS) {
do {
if (!reqHeaders.containsKey(Constants.ORIGIN)) {
log.info("CorsFilter filter ORIGIN");
break;
}
String origin = reqHeaders.getFirst(Constants.ORIGIN).toString();
URI originUri = URI.create(origin);
if (uri.getScheme().equals(originUri.getScheme()) && uri.getPort() == originUri.getPort() && uri.getHost().equals(originUri.getHost()))
break;
HttpHeaders respHeaders = exchange.getResponse().getHeaders();
respHeaders.setContentLength(0);
respHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
exchange.getResponse().setStatusCode(HttpStatus.NO_CONTENT);
respHeaders.set(Constants.Access_Control_Allow_Origin, "*");
respHeaders.set(Constants.Access_Control_Allow_Credentials, "true");
respHeaders.set(Constants.Access_Control_Allow_Methods, "GET, POST, PUT, DELETE, OPTIONS");
if (reqHeaders.containsKey(Constants.Access_Control_Request_Headers)) {
String accessControlRequestHeaders = reqHeaders.getFirst(Constants.Access_Control_Request_Headers);
respHeaders.set(Constants.Access_Control_Allow_Headers, accessControlRequestHeaders);
}
return exchange.getResponse().setComplete();
} while (true);
return chain.filter(exchange);
} else {
if (CollectionUtils.isEmpty(exchange.getResponse().getHeaders().get(Constants.TraceId))) {
Optional.ofNullable(MDC.get(Constants.TraceId)).ifPresent((t) -> {
exchange.getResponse().getHeaders().add(Constants.TraceId, t);
});
}
exchange.getResponse().getHeaders().add(Constants.Access_Control_Allow_Origin, "*");
exchange.getResponse().getHeaders().set(Constants.Access_Control_Allow_Credentials, "true");
exchange.getResponse().getHeaders().set(Constants.Access_Control_Allow_Methods, "GET, POST, PUT, DELETE, OPTIONS");
}
return chain.filter(exchange);
}
}
由于Netty的bug 请求头会重复,所以需要自己写方法去掉重复的返回头 自定义Ordered 在NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER 之后执行
package com.baozun.bs.moncler.web.filter.cors;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Optional;
import java.util.Set;
@Slf4j
@Component("corsResponseHeaderFilter")
public class CorsRemoveHeaderFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 21;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Optional.ofNullable(MDC.get(Constants.TraceId)).ifPresent((t) -> {
exchange.getResponse().getHeaders().add(Constants.TraceId, t);
});
return chain.filter(exchange).then(Mono.defer(() -> {
//移除重复的返回头
HttpHeaders rpsHeaders = exchange.getResponse().getHeaders();
Set<String> headers = Constants.headers();
for (String header : headers) {
if(rpsHeaders.containsKey(header)) {
String value = rpsHeaders.getFirst(header);
exchange.getResponse().getHeaders().remove(header);
if (Constants.Access_Control_Allow_Methods.equals(header)) {
value = Constants.ACCESS_CONTROL_ALLOW_METHODS_VALUE;
}
exchange.getResponse().getHeaders().set(header, value);
}
}
return chain.filter(exchange);
}));
}
}
package web.filter.cors;
import java.util.HashSet;
import java.util.Set;
public class Constants {
public static final String ORIGIN = "Origin";
public static final String Access_Control_Allow_Origin = "Access-Control-Allow-Origin";
public static final String Access_Control_Allow_Credentials = "Access-Control-Allow-Credentials";
public static final String Access_Control_Allow_Methods = "Access-Control-Allow-Methods";
public static final String Access_Control_Request_Headers = "Access-Control-Request-Headers";
public static final String Access_Control_Allow_Headers = "Access-Control-Allow-Headers";
public static final String TraceId = "X-B3-TraceId";
public static final String X_FRAME_OPTIONS = "X-Frame-Options";
public static final String X_XSS_Protection = "X-XSS-Protection";
public static final String VARY = "Vary";
public static final String ACCESS_CONTROL_ALLOW_METHODS_VALUE ="GET,POST,PUT,DELETE,OPTIONS";
public static Set<String> headers = new HashSet<>();
static {
headers.add(Access_Control_Allow_Origin);
headers.add(Access_Control_Allow_Credentials);
headers.add(Access_Control_Allow_Methods);
headers.add(Access_Control_Request_Headers);
headers.add(Access_Control_Allow_Headers);
headers.add(VARY);
headers.add(TraceId);
headers.add(X_FRAME_OPTIONS);
headers.add(X_XSS_Protection);
}
public static Set<String> headers(){
return headers;
}
}
以上方式完美解决了webflux getaway 跨域问题。但是美中不足由于时间紧代码有些乱。
闲下来之后查了webflux官网发现有其他好的解决方法 重写CorsWebFilter
用 CorsWebFilterConfiguration 代替CorsFilter
package web.filter.cors;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Component
@NoArgsConstructor
@Setter
@Getter
public class CorsWebFilterConfiguration {
@Bean
CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
//允许全部域名
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.setAllowedMethods(Arrays.asList(Constants.ACCESS_CONTROL_ALLOW_METHODS_VALUE.split(",")));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
总结:
遇见问题可用先查看对应技术的官方文档这样可以避免一些时间的浪费,实际的使用过程中还是会遇到一些问题,比如 没有去掉多余的返回头 测试环境是好的,但是发布到 uat 又不行了,后来继续排查发现是NettyWriteResponseFilter的bug。以上是一些遇到问题的分享希望可以帮助到需要的朋友
本人公众号 会不定期推送一些文章 ,有兴趣可以关注