跨域

124 阅读3分钟

同源是什么?

浏览器为了用户隐私,设置了严格的同源策略。
所谓同源策略,就是说如果 JS 运行在源 A 中,那么就只允许 JS 获取源 A 的数据,不允许获取源 B 或 C 或 D 的数据
那如果 mywebsite.com 引用了 cdn.com/jQuery.js,这算是跨域吗?
不算,因为 jQuery.js 是运行在 baidu.com 中的,他和 cdn.com 毫无关系——虽然 jQuery.js 是从他那里下载的。

这里所谓的“同源”指的是三个相同:

  1. 协议相同
  2. 域名相同
  3. 端口相同

比如说 https://www.baidu.com:8080 中,https:// 就是协议,www.baidu.com 就是域名,:8080 就是端口号。
这三个东西必须严格相同,否则浏览器将视为不同源

什么是跨域?

跨域资源共享,用于让网页的受限资源能够被其他域名的页面访问的一种机制。
通过该机制,页面能够自由地使用不同源的图片、样式、脚本、iframes 以及视频。
一些跨域的请求常常会被同源策略所禁止的。
跨源资源共享定义了一种方式,为的是浏览器和服务器之间能互相确认是否足够安全以至于能使用跨源请求

CORS

CORS 是一个需要浏览器和服务器同时支持的技术。
首先需要先进行声明,在 a 网站响应头里写上,b 网站可以访问就行了!
涉及该操作的属性是Access-Control-Allow-Origin。顾名思义嘛,访问-控制-允许-源。
比如 Node.js 的后台程序,可以写成如下形式:

if (path === 'user.json') {
  response.setHeader('Content-Type', 'text/json;charset=utf-8');
  response.setHeader('Access-Control-Allow-Origin', 'https://b.com');
  response.end();
}

就可以使 b 网站成功通过 Ajax 拿到 a 网站的 user.json。

JSONP

JSONP 和 JSON,有什么关系嘛?半毛钱关系都没有。

当没有 CORS 的时候,大家的跨域方式就是 JSONP。
我们虽然不能获取 https://baidu.com/user.json,但是我们可以获取 https://baidu.com/user.js 啊。
那我们能不能将数据放在 user.js 里面,例如:

window.data = {{ data }}

然后在后台服务器中,当我们访问的路径是 https://baidu.com/user.js 时,后台就将替换 {{ data }} 为 user.json 的数据。
那么在我们希望实现跨域的网站中,写上:

const script = document.createElement('script');
script.src = 'https://baidu.com/user.js';
document.body.appendChild(script);

这个时候,我们打开网页控制台,输入:window.data,就能看到刚刚替换的 json 数据了!
如果想要拿到数据,可以给刚刚创建的 script 标签,添加一个 onload 事件。

script.onload = () => {
  console.log(window.data);
};

如果觉得这样比较丑,我们还可以用另一种方式:函数。
在 user.js 中,我们不写 window.data = {{ data }} 了,我们写 window.getData({{ data }})
那么当我们请求的时候,是不是这个函数就被执行了?
所以只要在我们自己的 js 中写:

window.getData = data => {
  console.log(data);
};

当然,我们为了避免重名,所以可以用一个骚操作——用随机数

const random = Math.random().toString();
window[random] = data => {
  console.log(data);
};

...一些操作...

script.src = `https://baidu.com/user.js?callback=${random}`;

接下来在后台程序里拿到我们的查询参数,将 user.js 直接整个的写成 window[query.callback]( jsonData )

这种方式看起来就高级很多了,这就是一个回调呀,跨域的回调!
但实际上,这种办法还是需要进行 referer 检查,不然依然是不安全的

封装 JSONP

上面这么长一串代码,封装一下吧!

function jsonp(url) {
  return new Promise((resolve, reject) => {
    const random = Math.random().toString();
    window[random] = data => {
      resolve(data);
    };
    const script = document.createElement('script');
    script.src = `${url}?callback=${random}`;
    script.onload = () => {
      script.remove();
    };
    script.onerror = () => {
      reject();
    };
    document.body.appendChild(script);
  });
}

调用的时候只需要:

jsonp('https://baidu.com/user.js').then(data => {
  console.log(data);
});

最后,这里的数据是不仅仅可以放 JSON,我们可以放任何东西,比如 xml。(所以叫做 JSONP 是比较不合理的)