前言
前后端分离开发时,我们经常会遇到跨域问题
什么是跨域?
什么是同源策略及其限制内容?
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指”协议+域名+端口”三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

请求跨域了,那么请求到底发出去没有?
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
解决
跨域问题如何解决?
绝大多数解决方式无非是以下三类
服务器设置为允许跨域访问,后端实现 CORS
使用中间件代理、过滤器或者nginx反向代理等,通过同源策略对服务器不加限制
利用 iframe、script标签没有跨域限制的漏洞,或者使用websocket请求,规避掉跨域问题
这里我只推荐这以下两种方式跨域,其它的跨域方式都还有很多但都不推荐
| 开发环境 | 生产环境 |
| :——: | :——: |
| cors | cors |
| proxy | nginx |
前端解决方式
在
dev开发模式下可以下使用 webpack 的proxy使用也是很方便,参照 文档 就会使用了。但这种方法在生产环境是不能使用的。在生产环境中需要使用nginx进行反向代理。不管是proxy和nginx的原理都是一样的,通过搭建一个中转服务器来转发请求规避跨域的问题。
后端解决方式
CORS
CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
cors全称为 Cross Origin Resource Sharing(跨域资源共享)。这种方案对于前端来说没有什么工作量,和正常发送请求写法上没有任何区别,工作量基本都在后端这里。每一次请求,浏览器必须先以OPTIONS请求方式发送一个预请求(也不是所有请求都会发送 options,展开介绍 点我),通过预检请求从而获知服务器端对跨源请求支持的HTTP方法。在确认服务器允许该跨源请求的情况下,再以实际的HTTP请求方法发送那个真正的请求。推荐的原因是:只要第一次配好了,之后不管有多少接口和项目复用就可以了,一劳永逸的解决了跨域问题,而且不管是开发环境还是正式环境都能方便的使用。

在spring cloud gateway项目中解决跨域
增加以下两个类既可
package com.him.gateway.config;
import com.him.gateway.filter.CorsResponseHeaderFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
/**
* 配置跨域问题
*
* @author liaoyuxing
* @date 2021-5-20
*/
@Configuration
public class CorsConfig {
@Bean
public CorsResponseHeaderFilter corsResponseHeaderFilter() {
return new CorsResponseHeaderFilter();
}
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(600L);
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
package com.him.gateway.filter;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
/**
* 跨域请求头重复处理过滤器
*
* @author witleo
* @date 2021-5-20
*/
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
// 指定此过滤器位于NettyWriteResponseFilter之后
// 即待处理完响应体后接着处理响应头
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);
}));
}
}
参考文章: