美文网首页
跨域资源共享 CORS

跨域资源共享 CORS

作者: 止于秋分 | 来源:发表于2017-06-07 14:38 被阅读0次

*原文地址 *

通常用于浏览器像跨源服务器发送 XMLHttpRequest 请求,解决了 AJAX 只能同源使用的问题。因此实现 CORS,需要浏览器和服务器的双向支持。幸运的是,所有的现代浏览器都支持该功能,IE10 及以上。

JSONP (JSON with Padding)

虽然 JSONPJSON 一字之差,但是实则天差地别。
因为浏览器请求的同源策略,导致跨域访问出现问题。但是 <script> 中的 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);
}

前三句的意思是动态构建一个 <script> 标签插入到 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 请求,请求之后发现下面错误:

QQ20170602-143403@2x.png

大概就是说:我们的预请求的 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()));
}
image.png

成功了,我们如愿的得到了返回数据。
但是,这是怎么实现的呢?

如果你足够细心,不难发现上面图片中 list 请求发了两次。仅仅是重复吗?
当然不是,

image.png

第一个请求类型是 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 请求。

image.png

请求成功后,服务器会在 Response Header 中追加以下内容。

image.png

而对于上面我们写的那个 AJAX 请求虽然是 GET 类型,但是我们自定义了请求头内容,添加了 Authorization 字段,所以浏览器将这个请求视为 非简单请求

对于 非简单请求,会在 CORS 正式通信之前增加一次 预检请求(preflight),此请求为 OPTIONS 类型,只有预检请求返回成功,才会继续 CORS 请求。

image.png

如上图所示,预检请求除了会在请求头添加 Origin 字段外,还增加了两块头部信息。

Access-Control-Request-Headers: authorization, content-type,
Access-Control-Request-Method: GET

其中,Access-Control-Request-Headers 中的字段没有值,而我们的后台在进行 token 身份验证的时候一般会从 header 中获取对应的 token,导致验证不能通过。这就要求后台在进行 token 过滤验证的时候,将预检请求设置为白名单。这个确实是个坑...

上面的对 Spring BootCORS 配置只是针对于单个方法或单个类,当然我们也可以通过全局配置使其全局生效。

@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

相关文章

网友评论

      本文标题:跨域资源共享 CORS

      本文链接:https://www.haomeiwen.com/subject/bgaofxtx.html