CORS 跨域

1 什么是跨域问题

基于安全考虑,浏览器 会限制使用脚本发起任何跨域请求。

所谓的跨域请求,就是与当前页面的 http/ip/port 不一样的请求。

但在实际运用中,跨域获取数据的需求越来越强烈。

在标准没有出现之前,就出现了很多方法。广为使用的一种就是 JSONP。

1.1 JSONP

虽然浏览器有跨域请求的限制,但是一直以来,页面中的 src 元素链接的数据是可以跨域的:

<script src="http://jquery.com/jquery.js"></script>
<img src="http://yigehenhaodewangzhan.com/meinv.jpg"/>

所以很多人就基于这一漏洞(特性),开发出了一种跨域套路,称为 jsonp。

很简单:

// 定义函数,用来加载跨域(脚本)数据
function loadScript(src) {
    const script = document.createElement('script');
    script.src = src;
    document.body.appendChild(script);
}

// 然后,就可以使用上述函数,从任何服务器获取数据了
// 获取的数据被嵌入到页面的 <script>数据</script> 中,然后被当做脚本运行
// 所以,我们可以把数据封装为一段可以运行的脚本,比如:
//   foo({'name': '张三', 'age': 22})
// 这样,只要我们在页面中存在 foo 函数,这个函数就会被调用,
// 间接地,我们就可以获取并使用服务器得到的“怪异的数据”了
loadScript('http://另一个网站.com/api');

// 这是页面中存在的 foo 方法
function foo (data) {
    console.log(data);
}

另外,jQuery 对这种 jsonp 的方式做了很好的封装,使用起来特别简单:

$.ajax({
    method: 'get',
    url: 'http://再一个网站.com/api',
    dataType: 'jsonp',  // 返回的类型
    jsonp: 'callback',
    jsonpCallback='xxx'
}).done(console.log);

1.2 CORS (Cross Origin Resource Share)

一年一年又一年,过去了很多年,所有人只能忍受跨域的问题,想尽各种办法曲线救国。

终于,等待了很多年之后,跨域的官方标准出现了,他就是 CORS。

它的实现原理很简单:

天王盖地虎,宝塔镇河妖

浏览器 说,如果想让我显示跨域请求获取的数据,是可以的,不过要满足我的要求:

  1. 我,浏览器,在用户发送跨域请求的时候,会自动在请求 head 里追加 Origin 字段
  2. 你,服务器,如果允许我访问你的数据,你就在返回的时候在响应 head 里添加 Access-Control-Allow-Origin 字段
  3. 我如果能从你返回的数据里发现 Access-Control-Allow-Origin 并核对正确,就向用户渲染数据,否则。。一概跨域错误!!!

一个成功的跨域请求的发送、响应信息为:

# ---请求---
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
...
Origin: http://foo.example


# ---响应---
HTTP/1.1 200 OK
Content-Type: application/xml
...
Access-Control-Allow-Origin: *

# ---数据---
[XML Data]

当然,CORS 跨域方式不仅能处理普通的 GET 请求,它也支持其他一些复杂请求。

对于一些复杂请求,浏览器需要一个预检过程(Preflight)。这主要是防止:

浏览器费了好大劲把请求发送到服务器;服务器又费了很大劲将响应返回给客户端后;浏览器却发现请求不符合跨域标准,果断抛跨域错误

这样,那些花费不久白费了吗????

当然,预检还能从服务器获取自己要发送数据的注意事项(可以以什么 method 发送,要携带什么样的请求 head 等)。 如果预检都不合格,那么就没后续了,也就避免浪费了。

preflight 是一个 Option 请求,不带数据,但带着额外的请求头:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

对于 preflight 请求,浏览器应该要有相应的回应:

Access-Control-Allow-Origin: http://foo.example         # 是不是支持跨域
Access-Control-Allow-Methods: POST, GET, OPTIONS        # 你后续的请求可以使用啥方法
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type # 你可以携带什么样的 http 头
Access-Control-Max-Age: 86400                           # 这段预检结果浏览器你可以缓存,但不要超过这个时间

另外,为了安全(。。。。就是为了安全),默认在跨域请求中,浏览器是不会将 cookie 等信息连带发送的。

如果想发送这些信息咋办?告知浏览器:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';

function callOtherDomain(){
  if(xhr) {
    xhr.open('GET', url, true);
    xhr.withCredentials = true;  // 就是这句。在 fetch 和 jQuery 中有对应,自己尝试
    xhr.onreadystatechange = handler;
    xhr.send();
  }
}

2 手动解除浏览器跨域限制

将谷歌浏览器的跨域限制给去除,只需要在启动的快捷方式里添加参数即可:

--disable-web-security --user-data-dir=d:\xxx

然后,开启浏览器,就会没有跨域限制了。

虽然这样可以很方便进行调试跟开发,但是,用完请关闭。不安全。

Author: unname

Created: 2019-03-22 周五 01:32

Go ahead, never stop.