Spring Cloud GateWay 解决跨域问题并兼容

Spring Cloud GateWay 解决跨域问题并兼容IE & 重复 Request Headers处理方法

一、Spring Cloud GateWay解决跨域问题并兼容IE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
js复制代码@Configuration
public class GlobalCorsConfig {
private static final String MAX_AGE = "86400L";

@Bean
public WebFilter corsFilter() {

return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();

//允许所有域名进行跨域调用
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
if (requestMethod != null) {//适配IE
//放行全部原始头信息
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders().toString().replace("[", "").replace("]", ""));
//允许所有请求方法跨域调用
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
//允许跨域发送cookie
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
//获取除[Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma]其他全部字段
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
//请求有效期
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);
};
}

}

二、Gateway处理重复Request Headers的方法

2.1 跨域完成后,出现重复header,报出以下错误:

1
js复制代码origin xxxx has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'xxxx, xxxx', but only one is allowed.The 'Access-Control-Allow-Origin' header contains multiple values 'xxxx,xxxx', but only one is allowed.

2.2 处理方法:

方法一:

由于我用的Spring Cloud是较高的版本Hoxton.SR9,这个版本中有 DedupeResponseHeaderGatewayFilterFactory,贴出源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
js复制代码import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class DedupeResponseHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<DedupeResponseHeaderGatewayFilterFactory.Config> {
private static final String STRATEGY_KEY = "strategy";

public DedupeResponseHeaderGatewayFilterFactory() {
super(DedupeResponseHeaderGatewayFilterFactory.Config.class);
}

public List<String> shortcutFieldOrder() {
return Arrays.asList("name", "strategy");
}

public GatewayFilter apply(DedupeResponseHeaderGatewayFilterFactory.Config config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
DedupeResponseHeaderGatewayFilterFactory.this.dedupe(exchange.getResponse().getHeaders(), config);
}));
}

public String toString() {
return GatewayToStringStyler.filterToStringCreator(DedupeResponseHeaderGatewayFilterFactory.this).append(config.getName(), config.getStrategy()).toString();
}
};
}

void dedupe(HttpHeaders headers, DedupeResponseHeaderGatewayFilterFactory.Config config) {
String names = config.getName();
DedupeResponseHeaderGatewayFilterFactory.Strategy strategy = config.getStrategy();
if (headers != null && names != null && strategy != null) {
String[] var5 = names.split(" ");
int var6 = var5.length;

for(int var7 = 0; var7 < var6; ++var7) {
String name = var5[var7];
this.dedupe(headers, name.trim(), strategy);
}

}
}

private void dedupe(HttpHeaders headers, String name, DedupeResponseHeaderGatewayFilterFactory.Strategy strategy) {
List<String> values = headers.get(name);
if (values != null && values.size() > 1) {
//过滤过程
switch(strategy) {
case RETAIN_FIRST:
headers.set(name, (String)values.get(0));
break;
case RETAIN_LAST:
headers.set(name, (String)values.get(values.size() - 1));
break;
case RETAIN_UNIQUE:
headers.put(name, (List)values.stream().distinct().collect(Collectors.toList()));
}

}
}

public static class Config extends NameConfig {
private DedupeResponseHeaderGatewayFilterFactory.Strategy strategy;

public Config() {
//默认给定过滤规则=RETAIN_FIRST
this.strategy = DedupeResponseHeaderGatewayFilterFactory.Strategy.RETAIN_FIRST;
}

public DedupeResponseHeaderGatewayFilterFactory.Strategy getStrategy() {
return this.strategy;
}

public DedupeResponseHeaderGatewayFilterFactory.Config setStrategy(DedupeResponseHeaderGatewayFilterFactory.Strategy strategy) {
this.strategy = strategy;
return this;
}
}

public static enum Strategy {
//过滤规则
//[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
RETAIN_FIRST,
RETAIN_LAST,
RETAIN_UNIQUE;

private Strategy() {
}
}
}

由源码中可以看出DedupeResponseHeader的过滤规则为RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST。而且在DedupeResponseHeaderGatewayFilterFactory初始化时已经给定了默认的过滤规则为RETAIN_FIRST

1
2
3
js复制代码RETAIN_FIRST=过滤取第一个值
RETAIN_LAST=过滤取最后一个值
RETAIN_UNIQUE=过滤取唯一的值

DedupeResponseHeader可以配置三种规则中任一种规则,过滤规则和过滤参数以逗号,分割。

1
js复制代码spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=A B C D,[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]

或在配置中,也可以省略过滤规则,DedupeResponseHeaderGatewayFilterFactory会自动给定RETAIN_FIRST为默认过滤规则。

1
js复制代码spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=A B C D

yml配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
js复制代码spring:
application:
name: gateway-server2
cloud:
gateway:
discovery:
locator:
# 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
enabled: true # 是否开启基于服务发现的路由规则
lower-case-service-id: true # 是否将服务名称转小写
#gateway跨域后 Header重复过滤,过滤规则[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
default-filters[0]: DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials Access-Control-Expose-Headers Access-Control-Allow-Methods Access-Control-Allow-Headers Content-Type Vary,RETAIN_FIRST

方法二:

直接继承GlobalFilter, Ordered复写 filter() 过滤exchange.getResponse().getHeaders()中的headers属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
js复制代码@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);
}));
}
}

三、GateWay全局过滤器向request header中添加参数

由于在项目中从nginx请求到gateway,需要将获取到的权鉴数据存到httpRequest请求体中,供下游逻辑服务使用。

废话不多说,直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
js复制代码@Component
@Slf4j
public class AccessFilter implements GlobalFilter, Ordered {

@Autowired
private RedisTemplate redisTemplate;

@Autowired
private RestTemplate restTemplate;

//authMap
private JSONObject jsonMap;

@Autowired
private CustomConfiguration customConfiguration;

private static boolean isContains(String container, String[] regx) {
for (int i = 0; i < regx.length; i++) {
if (container.indexOf(regx[i]) != -1) {
return true;
}
}
return false;
}


@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();

String includes = "/public;/auth-authentication;/admin/auth-license/addCmsFiles";
String[] excludeList = customConfiguration.getExcludeUrl().split(";");
if (isContains(request.getURI().toString(), excludeList)) {
return chain.filter(exchange);
}

//请求体内容重写
ServerHttpRequest httpRequest = requestHeadersRePut(request, jsonMap);
ServerWebExchange newExchage = exchange.mutate().request(httpRequest).build();
return chain.filter(newExchage);
}

/**
* auth参数存入
* request header
*
* @param request
* @param authMap
* @return
*/
private ServerHttpRequest requestHeadersRePut(ServerHttpRequest request, JSONObject authMap) {
ServerHttpRequest httpRequest;
Map<String, String> map = new HashMap<>();

Consumer<HttpHeaders> httpHeaders = httpHeader -> {
httpHeader.set("userId", authMap.getString("userId"));
httpHeader.set("departmentId", authMap.getString("departmentId"));
try {
httpHeader.set("realName", URLEncoder.encode(authMap.getString("realName"), "UTF-8"));
} catch (UnsupportedEncodingException e) {
log.info("{} ===>putRequestHeaders, exceptiopn: {}", this.getClass().getSimpleName(), e.toString());
e.printStackTrace();
}
httpHeader.set("username", authMap.getString("username"));
httpHeader.set("authorization", authMap.getString("authorization"));
};
httpRequest = request.mutate().headers(httpHeaders).build();
return httpRequest;
}

@Override
public int getOrder() {
return 0;
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%