有时候,我们需要对所有微服务跨域请求进行处理.
(资料图)
跨域的说明:
哪些场景是跨域:不同的系统进行AJAX的请求的时候属于跨域的。 跨域的请求一般是不被允许的。
1.www.jd.com---->www.taobao.com 跨域
2.localhost:8001 —>localhost:8002 跨域
3.www.jd.com:80—>www.jd.com:81 跨域
4.https —>http 跨域。
域名相同,端口相同,协议相同 就不是跨域。
解决跨域的问题的解决方案:CORS的协议 (高版本的浏览器至少 IE10 google)在服务端进行注解配置(相当配置了CORS的配置项)springmvc的注解 @CrossOrigin通过在网关(spring cloud gateway)进行统一的配置通过在网关(nginx里面进行配置CORS)JSONP的方式(了解) 利用JS的漏洞的实现跨域,而且只支持GET请求 不支持其他的请求。解决方式涉及到图如下:
通过@CrossOrigin()注解实现
如果都需要跨域处理,该方式需要在每一个Controller类上配置。
另外我们一般在SpringCloudGateway网关配置
spring: cloud: gateway: globalcors: corsConfigurations: "[/**]": allowedOriginPatterns: "*" allowed-methods: "*" allowed-headers: "*" allow-credentials: true exposedHeaders: "Content-Disposition,Content-Type,Cache-Control"
如果不想配置到yml也可以代码写在网关模块中
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpMethod;import org.springframework.http.HttpStatus;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.web.cors.reactive.CorsUtils;import org.springframework.web.server.ServerWebExchange;import org.springframework.web.server.WebFilter;import org.springframework.web.server.WebFilterChain;import reactor.core.publisher.Mono;/** * 跨域配置 */@Configurationpublic class CorsConfig{ /** * 这里为支持的请求头,如果有自定义的header字段请自己添加 */ private static final String ALLOWED_HEADERS = "X-Requested-With, Content-Type, Authorization, credential, X-XSRF-TOKEN, token, Admin-Token, App-Token"; private static final String ALLOWED_METHODS = "GET,POST,PUT,DELETE,OPTIONS,HEAD"; private static final String ALLOWED_ORIGIN = "*"; private static final String ALLOWED_EXPOSE = "*"; private static final String MAX_AGE = "18000L"; @Bean public WebFilter corsFilter() { return (ServerWebExchange ctx, WebFilterChain chain) -> { ServerHttpRequest request = ctx.getRequest(); if (CorsUtils.isCorsRequest(request)) { ServerHttpResponse response = ctx.getResponse(); HttpHeaders headers = response.getHeaders(); headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS); headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS); headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN); headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE); headers.add("Access-Control-Max-Age", MAX_AGE); headers.add("Access-Control-Allow-Credentials", "true"); if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); return Mono.empty(); } } return chain.filter(ctx); }; }}
前端页面通过不同域名或IP访问SpringCloud Gateway,那么,此时直连微服务和网关的跨域问题都解决了,是不是很完美?
No~ 问题来了,前端仍然会报错:“不允许有多个’Access-Control-Allow-Origin’ CORS头”。
问题:Vary 和 Access-Control-Allow-Origin 两个头重复了两次,其中浏览器对后者有唯一性限制!
解决方式之一:手动写一个 CorsResponseHeaderFilter 的 GlobalFilter 去修改Response中的头。
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.ArrayList;import java.util.List;import java.util.stream.Collectors;@Componentpublic class CorsResponseHeaderFilter implements GlobalFilter, Ordered { private static final String ANY = "*"; @Override public int getOrder() { // 指定此过滤器位于NettyWriteResponseFilter之后 // 即待处理完响应体后接着处理响应头 return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1; } @Override @SuppressWarnings("serial") public Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange).then(Mono.fromRunnable(() -> { 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) || kv.getKey().equals(HttpHeaders.VARY))) .forEach(kv -> { // Vary只需要去重即可 if(kv.getKey().equals(HttpHeaders.VARY)) kv.setValue(kv.getValue().stream().distinct().collect(Collectors.toList())); else{ List value = new ArrayList<>(); if(kv.getValue().contains(ANY)){ //如果包含*,则取* value.add(ANY); kv.setValue(value); }else{ value.add(kv.getValue().get(0)); // 否则默认取第一个 kv.setValue(value); } } }); })); }}
基于SpringCloudGateway网关配置完成。