*原文地址 *
通常用于浏览器像跨源服务器发送 XMLHttpRequest
请求,解决了 AJAX
只能同源使用的问题。因此实现 CORS
,需要浏览器和服务器的双向支持。幸运的是,所有的现代浏览器都支持该功能,IE10 及以上。
JSONP (JSON with Padding)
虽然 JSONP
和 JSON
一字之差,但是实则天差地别。
因为浏览器请求的同源策略,导致跨域访问出现问题。但是 中的
src
且并无这限制。并且 json
又受到 javascript
良好的原生支持。有人便想到了利用 javascript
去获取 json
数据。如下:
var script = document.createElement('script');
script.src = 'http://localhost:8080/api/auth/blog/listByJSONP/?jsonp=demo';
document.body.insertBefore(script, document.body.firstChild);
function demo(json) {
console.log(json);
}
前三句的意思是动态构建一个 标签插入到
body
头部,src
包含了一个参数价值对 jsonp=demo
,指明了数据获取到后的回调函数。
这还没完,我们还需要后台的配合,返回一个我们能够识别的数据。什么叫能够识别的数据呢?
我们之前传了一个键值对到后台,获取到键值对后,进行拼装,最终我们希望得到的返回数据是这样的:
demo({state: 'success'});
// demo({
// state: 'success'
// });
嗯,。刚好是 javascript
的一个方法的执行语句,我们又正好定义了一个 demo
方法,执行就输出了我们要的 json
数据,妙啊!
可能你会心一笑之后,突然就意识到了一个问题,src
只能发 GET
请求的,所以这种方法只能适用于 GET
请求,而且需要对后台的返回数据进行特殊的约定。虽然 jquery
对其不辞劳苦的进行封装,使它的使用方法像 ajax
一样,但是这些硬伤决定了它不能成为主流的解决方案,只能算是对 IE 老版本的一种救赎。嗯,最起码我是这么认为的。
CORS (Cross-origin resource sharing)
CORS
是一个 W3C 标准,得到了所有现代浏览器的支持。
CORS
什么样?
$.ajax({
type: 'GET',
url: 'http://localhost:8080/api/auth/blog/list',
beforeSend: function(request) {
request.setRequestHeader("Authorization", "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJCYW9YdWViaW4iLCJyb2xlIjoiMiIsImNyZWF0ZWQiOjE0OTYzNzE4ODA5ODYsImV4cCI6MTQ5NjM3MjQ4MH0.HcTn9ccWigEuNB0E4VBrUZJAraDp_pi0HBecApBZKj4");
},
success: function(data) {
console.log(data);
}
});
以上我们写了一个最基本的 AJAX
请求,请求之后发现下面错误:
大概就是说:我们的预请求的 header 中没有
Access-Control-Allow-Origin
, 来自http://10.10.17.58:3000
源的请求不被允许访问。
因为我的后端服务是基于 Spring Boot
构建的,所以我这里着重于说明一下 Spring Boot
的解决方法。
@CrossOrigin
查阅相关文档后,给我们的方法添加 @CrossOrigin
注解。
@CrossOrigin(origins = "http://10.10.17.58:3000")
@GetMapping("auth/blog/list")
public ResponseEntity getBlogByPage(PageForm pageForm) {
return ResponseEntity.ok(blogService.getBlogItemByPage(pageForm.getPage() - 1, pageForm.getSize()));
}
成功了,我们如愿的得到了返回数据。
但是,这是怎么实现的呢?
如果你足够细心,不难发现上面图片中 list
请求发了两次。仅仅是重复吗?
当然不是,
第一个请求类型是 OPTIONS
,而且没有返回值
第二个请求才是我们想要发出的 GET
请求
浏览器将 CORS
请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
CORS
标准将 GET, HEAD, POST
定义为 简单HTTP方法,而将请求报头 Accept, Accept-Language, Content-Language
以及采用如下三种媒体类型的报头 Content-Type
称为 简单请求报头
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
对于 简单请求,浏览器直接在请求头信息中添加 Origin
字段,直接发送 CORS
请求。
请求成功后,服务器会在 Response Header
中追加以下内容。
而对于上面我们写的那个 AJAX
请求虽然是 GET
类型,但是我们自定义了请求头内容,添加了 Authorization
字段,所以浏览器将这个请求视为 非简单请求。
对于 非简单请求,会在 CORS
正式通信之前增加一次 预检请求(preflight),此请求为 OPTIONS
类型,只有预检请求返回成功,才会继续 CORS
请求。
如上图所示,预检请求除了会在请求头添加 Origin
字段外,还增加了两块头部信息。
Access-Control-Request-Headers: authorization, content-type,
Access-Control-Request-Method: GET
其中,Access-Control-Request-Headers
中的字段没有值,而我们的后台在进行 token
身份验证的时候一般会从 header
中获取对应的 token
,导致验证不能通过。这就要求后台在进行 token
过滤验证的时候,将预检请求设置为白名单。这个确实是个坑...
注
上面的对 Spring Boot
的 CORS
配置只是针对于单个方法或单个类,当然我们也可以通过全局配置使其全局生效。
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
// * 表示不限制,可以根据自己情况自行配置
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}
Spring Boot
还是很方便的。
参考
跨域资源共享 CORS 详解 - 阮一峰
CORS:跨域资源共享 W3C的CORS Specification
Enabling Cross Origin Requests for a RESTful Web Service