跨域:指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。

同源策略:是指协议域名端口都要相同,其中有一个不同都会产生跨域问题

1、跨域的流程

​ 跨域请求的实现是通过预检请求实现的,先发送一个OPTIONS探路,收到响应允许跨域后再发送真实请求。

2、解决办法1:使用nginx反向代理为同一域

​ 使用Nginx反向代理,不同地址、端口都被同一个域名反向代理了,这就是统一域了。这种方法在开发时没法用,所以不采用。


3、解决方法2:使用WebMvcConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {

@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedOrigins("")
.allowedMethods("*")
.allowCredentials(true)
.maxAge(3600);
}
};
}
}

​ 这个方法十分的简单,但是在实际场景中使用起来可能会遇到一些麻烦,比如我们现在需要对请求路径进行拦截,要实现这个需求就要添加拦截器,现在我们在上面的代码上添加拦截器的代码。

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
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
// 跨域配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders(allowedHeader)
.allowedOrigins(allowedOrigins)
.allowedMethods("*")
.allowCredentials(true)
.maxAge(3600);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册的路由拦截器
registry.addInterceptor(new SaRouteInterceptor())
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/login");
}
};
}
}

​ 使用上面这段代码时你会发现,被拦截的路径出现了跨域问题,而未被拦截的路径则可以正常请求,出现这种情况的原因则是由于请求在知道本次请求可以跨域之前就已经被拦下来了,自然就认为该站点不能允许跨域,就出现了跨域的情况。


4、解决方法3: 配置当前请求允许跨域(采用)

CORS:CORS 是一个 W3C 标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX只能同源使用的限制。

  • Access-Control-Allow-Origin : 支持哪些来源的请求跨域

  • Access-Control-Allow-Method : 支持那些方法跨域

  • Access-Control-Allow-Credentials :跨域请求默认不包含cookie,设置为true可以包含cookie

  • Access-Control-Expose-Headers : 跨域请求暴露的字段

    • CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
      Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
      如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
  • Access-Control-Max-Age :表明该响应的有效时间为多少秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将失效。


在网关中配置filter配置类,每个请求来了以后,返回给浏览器之前都添加上那些字段

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
//这个包别导错了,有一个很像的。
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class GulimallCorsConfiguration{

@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

CorsConfiguration corsConfiguration= new CorsConfiguration();
//1、配置跨域
// 允许跨域的请求头
corsConfiguration.addAllowedHeader("*");
// 允许跨域的请求方式
corsConfiguration.addAllowedMethod("*");
// 允许跨域的请求来源
corsConfiguration.addAllowedOriginPattern("*");
//注释的这句会报错。因为当allowCredentials为真时,allowedorigin不能包含特殊值"*",因为不能在"访问-控制-起源“响应头中设置该值。
//corsConfiguration.addAllowedOrigin("*");//这句会报错
// 是否允许携带cookie跨域
corsConfiguration.setAllowCredentials(true);

// 任意url都要进行跨域配置,两个*号就是可以匹配包含0到多个/的路径
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}

// 注册的路由拦截器
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaRouteInterceptor())
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/login");
}
};
}
}

​ 通过这两个bean,就可以实现跨域配置的同时进行路由拦截的需求。至于为什么CrosFilter可以做到Interceptor做不到的事情,是因为前者是过滤器后者是拦截器,过滤器是servlet提供的,而拦截器是spring提供的,过滤器的执行时机要早拦截器的,所以就可以在请求被拦截前允许跨域。