1、原因
1.1 概述原因
浏览器发现是跨域请求,就会自动在请求头中加上Origin字段,代表请求来自哪个域(协议+主机名+端口号)。
服务器在收到请求后,根据请求头中Origin字段值来判断是否允许跨域请求通过。
具体实现方法是:在响应头Access-Control-Allow-Origin字段中设置指定的域名,表示允许这些域名的跨域请求。
如果请求头中Origin字段的域名包含在这些域名中,则可以实现跨域请求(当然有时候还需要结合其他字段来判断),否则不通过
1.2 跨域细节
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
1.2.1 简单请求
对于简单请求(get ,post请求),浏览器直接发出CORS请求。具体来说,就是在Header中增加一个Origin字段。如果浏览器发现跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。
说明本次请求来自哪个源(协议+域名+端口)。
服务器根据Origin的值决定是否同意这次请求。
如果Origin指定的源在不在后端的许可白名单范围内,服务器会返回一个正常的http回应。
浏览器接收后发现,这个response的Header没有包含Access-Control-Allow-Origin字段,抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。
这种错误无法通过状态码识别,因此HTTP response的状态码有可能是200。
如果Origin指定的域名在许可的范围内,则服务器返回的相应中,会多出几个头信息字段
Access-Control-Allow-Origin: test.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8 Access-Control-Allow-Origin:(必须字段)它的值要么是请求时Origin的值,要么是,表示接受任意域名的请求。*
Access-Control-Allow-Credentials:(可选字段)它是一个bool值,表示是否允许发送Cookie。
默认情况下,Cookie不包括在CORS请求之中。设为true,表示服务器明确许可,
1.2.2. 非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
2、springboot下解决办法
对于 CORS的跨域请求,主要有以下几种方式可供选择:
- 返回新的CorsFilter
- 重写 WebMvcConfigurer
- 使用注解 @CrossOrigin
- 手动设置响应头 (HttpServletResponse)
- 自定web filter 实现跨域
2.1 返回新的CorsFilter
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放行哪些原始域 这里注意根据版本,高版本使用pattern
config.addAllowedOriginPattern("*");
//是否发送 Cookie
config.setAllowCredentials(true);
//放行哪些请求方式
config.addAllowedMethod("*");
//放行哪些原始请求头部信息
config.addAllowedHeader("*");
//暴露哪些头部信息
config.addExposedHeader("*");
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
2.2 重写WebMvcConfigurer(全局跨域)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
//是否发送Cookie
.allowCredentials(true)
//放行哪些原始域
.allowedOriginPatterns("*")
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
.allowedHeaders("*")
.exposedHeaders("*");
}
}
2.3 手动设置响应头 (HttpServletResponse)
使用 HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域,这里 Origin的值也可以设置为 “*”,表示全部放行。
@RequestMapping("/test")
public String test(HttpServletResponse response) {
response.addHeader("Access-Allow-Control-Origin","*");
return "Hello";
}
2.4 自定web filter 实现跨域
@Component
public class TestCorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
3、源码入口
org.apache.catalina.filters.CorsFilter#doFilter
从截图可以看到请求分为简单请求和非简单请求,以及预检。
总结
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
最终目的都是修改响应头,向响应头中添加浏览器所要求的数据,进而实现跨域
通过设置HTTP的响应头信息,告知浏览器哪些情况在不符合同源策略的条件下也可以跨域访问,浏览器通过解析Http协议中的Header执行具体判断。
CROS跨域常用header
- Access-Control-Allow-Origin: 允许哪些ip或域名可以跨域访问
- Access-Control-Max-Age: 表示在多少秒之内不需要重复校验该请求的跨域访问权限
- Access-Control-Allow-Methods: 表示允许跨域请求的HTTP方法,如:GET,POST,PUT,DELETE
- Access-Control-Allow-Headers: 表示访问请求中允许携带哪些Header信息,如:Accept、Accept-Language、Content-Language、Content-Type