Spring Cloud GateWay addresses cross-domain issues and is compatible with IE & Repeat Request Headers processing
Spring Cloud GateWay solves cross-domain problems and is compatible with IE
@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();
// Allow all domain names to make cross-domain calls
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
if(requestMethod ! =null) {//适配IE
// Release all original headers
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders().toString().replace("["."").replace("]".""));
// Allow all request methods to be invoked across domains
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
// Allow cross-domain cookie sending
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, "*");
// Request validity period
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
returnMono.empty(); }}returnchain.filter(ctx); }; }}Copy the code
Gateway handles duplicate Request Headers
2.1 Duplicate Header occurs after cross-domain completion, and the following error is reported:
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.
Copy the code
2.2 Treatment methods:
Method one:
Because I am using Spring Cloud is higher version Hoxton SR9, with DedupeResponseHeaderGatewayFilterFactory in this version, posted on the source code.
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() {
returnGatewayToStringStyler.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) {
// Filter process
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() {
// Default given filter rule =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 {
// Filter rules
//[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
RETAIN_FIRST,
RETAIN_LAST,
RETAIN_UNIQUE;
private Strategy(){}}}Copy the code
By the source code can be seen in the DedupeResponseHeader filtering rules for RETAIN_FIRST | RETAIN_UNIQUE | RETAIN_LAST. And upon initial DedupeResponseHeaderGatewayFilterFactory has given the default filtering rules for RETAIN_FIRST.
Tain_first = Filter for first value RETAIN_LAST= filter for last value RETAIN_UNIQUE= Filter for unique valueCopy the code
DedupeResponseHeader You can configure any of the three rules. Filter rules and filter parameters are separated by commas (,).
spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=A B C D,[RETAIN_FIRST|RETAIN_UNIQUE|RETAIN_LAST]
Copy the code
Or in the configuration, also can omit filtering rules, DedupeResponseHeaderGatewayFilterFactory automatically given RETAIN_FIRST as the default filtering rules.
spring.cloud.gateway.default-filters[0]=DedupeResponseHeader=A B C D
Copy the code
The YML configuration is as follows:
spring:
application:
name: gateway-server2
cloud: gateway: Discovery: locator: # Indicates whether to combine this parameter with the service discovery component and forward the packet to a specific service instance using the serviceId.enabled: trueWhether to enable the routing rule lower- based on service discoverycase-service-id: true# # if transfer service name lowercase gateway cross-domain Header after repeated filter, filter rules [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_FIRSTCopy the code
Method 2:
Inheriting GlobalFilter directly, Ordered override filter() filters the headers attribute in exchange.getresponse ().getheaders ()
@Component
public class CorsResponseHeaderFilter implements GlobalFilter.Ordered {
@Override
public int getOrder() {
// Remove duplicate CORS headers after NettyWriteResponseFilter processes the response body
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
@SuppressWarnings("serial")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
returnchain.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));
}}));
returnchain.filter(exchange); })); }}Copy the code
GateWay global filter adds parameters to the Request header
As nginx requests from the project to the gateway, the obtained authentication data needs to be stored in the httpRequest request body for use by downstream logical services.
Without further ado, let’s get right to the code.
@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);
}
// Request body content to be overwritten
ServerHttpRequest httpRequest = requestHeadersRePut(request, jsonMap);
ServerWebExchange newExchage = exchange.mutate().request(httpRequest).build();
return chain.filter(newExchage);
}
/** * auth saves 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; }}Copy the code