目录
- 一、什么是跨域
- 二、跨域资源共享(CORS)
-
- 1. 简单请求
- 2. 非简单请求
- 三、SpringBoot设置CORS
-
- 1. 配置过滤器CorsFilter
- 2. 实现接口WebMvcConfigurer
- 3. 使用注解@CrossOrigin
一、什么是跨域
请求url的协议、域名、端口三者有任意一个不同即为跨域。跨域问题是因为浏览器的同源策略的限制而产生的。
-
同源:请求url的协议、域名、端口三者都相同即为同源(同一个域)。
-
同源策略:同源策略(Sameoriginpolicy)是一种约定,他是浏览器最核心也最基本的安全功能。同源策略会阻止非同源(同一个域)的内容进行交互。
同源策略的限制:
- 无法读取非同源网页的Cookie、LocalStorage和IndexedDB
- 无法接触非同源网页的DOM
- 无法向非同源地址发送AJAX请求
浏览器的同源策略会限制跨域请求,限制的方式一般有两种:
- 浏览器限制发起跨域请求;
- 跨域请求可以正常发起,但是返回的结果被浏览器拦截了。
一般浏览器都是第二种方式限制跨域请求,那就是说请求已到达服务器,并有可能对数据库里的数据进行了操作,但是返回的结果被浏览器拦截了,那么我们就获取不到返回结果,这是一次失败的请求,但是可能对数据库里的数据产生了影响。
为了防止这种情况的发生,规范要求,对这种可能对服务器数据产生副作用的HTTP请求方法,浏览器必须先使用OPTIONS方法发起一个预检请求,从而获知服务器是否允许该跨域请求:如果允许,就发送带数据的真实请求;如果不允许,则阻止发送带数据的真实请求。
二、跨域资源共享(CORS)
解决非同源内容无法交互的问题,目前主流的解决方案就是:CORS(跨域资源共享)。
跨域资源共享(Cross-origin Resource Sharing)简称CORS,它突破了一个请求在浏览器发出只能在同源的情况下向服务器获取数据的限制。
CORS约定服务器端和浏览器在HTTP协议之上,通过一些额外HTTP头部信息,进行跨域资源共享的协商。服务器端和浏览器都必需遵循规范中的要求。
CORS把HTTP的跨域请求分成两类,简单请求和非简单请求,不同请求按照不同的策略进行跨域资源共享协商。
1. 简单请求
简单跨域请求需满足的条件:
-
请求方法是GET、HEAD或者POST(POST时,Content-Type的值必须是application/x-www-form-urlencoded、multipart/form-data、text/plain中的一个值);
-
请求中没有自定义HTTP请求头。
HTTP头只能时下面这些字段:
- Accept
- Accept-Language
- Content-Language
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type
以上两点都满足才是简单跨域请求。
对于简单跨域请求,处理方式如下:
-
浏览器要做的就是在HTTP请求头中添加Origin,将JavaScript脚本所在域填充进去,向其他域的服务器请求资源。
Origin: http://www.joker.com
Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
-
服务器端收到一个简单跨域请求后,根据资源权限配置,在响应头中添加Access-Control-Allow-Origin。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。 但这个响应头信息没有包含Access-Control-Allow-Origin字段,浏览器就知道该域名不在许可范围内。
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段:
Access-Control-Allow-Origin: http://www.joker.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: My-Token
Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*值,表示接受任意域名的请求。
Access-Control-Allow-Credentials: 该字段是可选的。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,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-Allow-Origin字段,来判断如果当前域已经得到授权,则将结果返回给JavaScript。否则浏览器忽略此次响应。
2. 非简单请求
非简单跨域请求需满足的条件:
-
除GET、HEAD和POST(Content-Type的值是:application/x-www-form-urlencoded、multipart/form-data、text/plain中的一个值)以外的其他HTTP方法
如:PUT、DELETE、TRACE、PATCH、POST(Content-Type的值是:application/json)。
-
请求中有自定义HTTP头部。
以上两点只要至少满足其中一点就是非简单跨域请求。
对于非简单跨域请求,处理方式如下:
-
浏览器在发送真实HTTP请求之前先发送一个OPTIONS的预检请求,检测服务器端是否支持真实请求进行跨域资源访问。
真实请求的信息在OPTIONS请求中通过请求头中的Access-Control-Request-Method和Access-Control-Request-Headers字段来描述。此外与简单跨域请求一样,请求头中也会有Origin字段。
Origin: http://www.joker.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: Header1,Header2
Origin:必须字段,用于指定请求源。
Access-Control-Request-Method:必须字段,用于描述真实请求的方法(PUT、DELETE等)。
Access-Control-Request-Headers:指定真实请求会额外发送的请求头字段信息。
-
服务器端接到预检请求后,会检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段,检验是否允许跨源请求。
如果不允许该跨域请求,会返回一个正常的HTTP回应,但这个响应头信息没有包含Access-Control-Allow-Origin字段,浏览器就知道该域名不在许可范围内。
如果允许该跨域请求,就会在响应头中放入Access-Control-Allow-Origin、Access-Control-Allow-Methods和Access-Control-Allow-Headers,分别表示允许跨域资源请求的域、请求方法和请求头。此外,服务器端还可以在响应头中放入Access-Control-Max-Age,允许浏览器在指定时间内,无需再发送预检请求进行协商,直接用本次协商结果即可。
Access-Control-Allow-Origin: http://www.joker.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: Header1,Header2,Header3 Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000
Access-Control-Allow-Methods:该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
Access-Control-Allow-Headers:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
Access-Control-Allow-Credentials: 该字段与简单请求时的含义相同。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
Access-Control-Max-Age: 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
-
浏览器根据OPTIONS请求返回的结果来决定是否继续发送真实的请求进行跨域资源访问。这个过程对真实请求的调用者来说是透明的。
三、SpringBoot设置CORS
SpringBoot设置CORS的的本质都是通过设置响应头信息来告诉前端该请求是否支持跨域。
SpringBoot设置CORS的方式主要有以下三种。
1. 配置过滤器CorsFilter
@Configuration
public class CorsConfig {
@Bean
CorsFilter corsFilter() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return new CorsFilter(source);
}
}
2. 实现接口WebMvcConfigurer
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*")
.allowCredentials(true);
}
}
3. 使用注解@CrossOrigin
@CrossOrigin注解可以用在类或者方法上
用在控制器类上,表示 该类的所有方法都允许跨域
@RestController
@CrossOrigin
public class TestController {
@GetMapping("test")
public String test() {
return "success";
}
}
用在控制器方法上,表示该方法都允许跨域
@RestController
public class TestController {
@CrossOrigin
@GetMapping("test")
public String test() {
return "success";
}
}
@CrossOrigin注解源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
/**
* 这origins和value是一样的
* 允许来源域名的列表,例如 www.baidu.com,匹配的域名是跨域预请求Response头中的Access-Control-Aloow_origin字段值。
* 不设置确切值时默认支持所有域名跨域访问。
*/
@AliasFor("origins")
String[] value() default {};
@AliasFor("value")
String[] origins() default {};
/**
* 高版本下Spring2.4.4使用originPatterns而不是value和origins
*/
String[] originPatterns() default {};
/**
* 跨域请求中允许的请求头中的字段类型, 该值对应跨域预请求Response头中的Access-Control-Allow-Headers字段值。
* 不设置确切值默认支持所有的header字段(Cache-Controller、Content-Language、Content-Type、Expires、Last-Modified、Pragma)跨域访问
*/
String[] allowedHeaders() default {};
/**
* 跨域请求请求头中允许携带的除Cache-Controller、Content-Language、Content-Type、Expires、Last-Modified、Pragma这六个基本字段之外的其他字段信息,
* 对应的是跨域请求Response头中的Access-control-Expose-Headers字段值
*/
String[] exposedHeaders() default {};
/**
* 跨域HTTP请求中支持的HTTP请求类型(GET、POST...),
* 不指定确切值时默认与 Controller 方法中的 methods 字段保持一致。
*/
RequestMethod[] methods() default {};
/**
* 浏览器是否将本域名下的cookie信息携带至跨域服务器中。默认携带至跨域服务器中,但要实现cookie共享还需要前端在AJAX请求中打开withCredentials属性。
* 该值对应的是是跨域请求 Response 头中的 'Access-Control-Allow-Credentials' 字段值。
*/
String allowCredentials() default "";
/**
* 该值的目的是减少浏览器预检请求/响应交互的数量。默认值1800s。设置了该值后,浏览器将在设置值的时间段内对该跨域请求不再发起预请求。
* 该值对应的是是跨域请求Response头中的Access-Control-Max-Age字段值,表示预检请求响应的缓存持续的最大时间。
*/
long maxAge() default -1;
}