说说跨域

说说跨域

本文写于 2020 年 7 月 17 日

大家有被盗号的经历吗?

就是那种,对盗来的号的每个好友发送一遍:“xxx,在吗?借我 200 块钱,江湖救急!”

这是多恐怖的事情。

假设你登录了网页版 QQ,我们知道所有的数据其实都是 JS 请求服务器得来的。

那如果我写一个“黑客网站”,你只要打开我的网站,我就会请求 QQ 的服务器,以得到你的 QQ 数据,这样就能向每一个好友借一遍钱(嘿嘿嘿)。

黑客网站发送的请求和官方网站发送的请求有什么区别呢?

答:refer 不同。

我们可以打开开发者工具,进入 Network,筛选出 XHR(就是 XMLHttpRequest 对象),就可以看到我们发送过的 Ajax 请求。请求中有一个特殊的字段:Referer,它就是发送请求的网站域名,比如百度发的请求,referer 就是 https://www.baidu.com

那既然如此,我们完全可以在后台检测 referer,这样就可以知道请求是从哪里发来的,从而实现「同源」。

但是万一程序员忘记了怎么办?浏览器应该主动的预防这种偷数据的行为!

同源是什么?

综上所述,浏览器为了用户隐私,设置了严格的同源策略。

所谓同源策略,就是说如果 JS 运行在源 A 中,那么就只允许 JS 获取源 A 的数据,不允许获取源 B 或 C 或 D 的数据——比如刚刚说的黑客 QQ 空间。

那如果 mywebsite.com 引用了 cdn.com/jQuery.js,这算是跨域吗?

不算,因为 jQuery.js 是运行在 baidu.com 中的,他和 cdn.com 毫无关系——虽然 jQuery.js 是从他那里下载的。

可以说浏览器安全的基石就是“同源政策”。

在 1995 年,同源政策由网景引入了浏览器。并且到目前为止,所有浏览器都在实行这个政策。

最初它的含义是指:A 网页设置的 Cookie,B 网页不能打开,除非这两个网页"同源"

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

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

协议、域名和端口这三样在我之前一篇文章中讲过,都是 URL 的组成部分。

比如说 https://www.baidu.com:8080 中,https:// 就是协议,www.baidu.com 就是域名,:8080 就是端口号。

这三个东西必须严格相同,否则浏览器将视为不同源

举几个例子:

  • baidu.comwww.baidu.com 不同源(域名不同);
  • www.baidu.com/swww.baidu.com 同源;
  • http://www.baidu.com:8080https://www.baidu.com:8080 不同源(协议不同);
  • baidu.com:8080baidu.com:80 不同源(端口不同)。

不同源好不好?

肯定不好呀,如果不同源,用户隐私就会受到严重的威胁!

刚刚说了如果没有同源的话,黑客网页可以「偷走」你正在访问的网页的一些数据,甚至他还能进行一些操作。这是怎么做到的呢?

其实就是 Cookie。所谓 Cookie 就是“小型文本文件”,是网站为了辨别用户身份而储存在浏览器本地上的数据。

如果 Cookie 包含了某些隐私信息——比如支付宝余额——这让黑客网站随便就拿到了,那该是多么可怕的一件事情。(甚至于 Cookie 有时会保存登录信息!)

因此,如果没有「同源策略」,黑客网站就能直接操作你的账户!

现在,随着互联网的发展,"同源政策"越来越严格。目前如果不是同源,共有三种行为受到限制:

  1. Cookie, LocalStorage, IndexDB 无法读取;
  2. DOM 无法获得;
  3. AJAX 请求不能发送。

跨域

刚刚说了很多因为「同源策略」而不能实现的操作。但是如果我真的需要操作这些怎么办呢?

比如两个网站有某些合作,甚至说这俩网站本来就都是我的,该怎么办?

这就需要用到跨域了。

先看看维基百科的解释:

跨域资源共享,用于让网页的受限资源能够被其他域名的页面访问的一种机制。

通过该机制,页面能够自由地使用不同源的图片、样式、脚本、iframes 以及视频。

一些跨域的请求常常会被同源策略所禁止的。

跨源资源共享定义了一种方式,为的是浏览器和服务器之间能互相确认是否足够安全以至于能使用跨源请求

1. CORS

跨域有许多种方法,先说目前最常见也是比较新的一种方式: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。

CORS 就这么简单。

“你们自己商量好,想要允许跨域的话只需要在请求头声明即可,但是数据泄漏了可与我无关。 ——浏览器”

2. 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 检查,不然依然是不安全的

处女座提示:

如果不希望拿到数据后 script 标签依然存在,那么可以用刚刚的 onload 事件,当 script 标签 onload 时,remove 自身。

封装 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 是比较不合理的)

并且 JSONP 花了我们这么多的篇幅,也就只实现了 CORS 一样的功能(并且因为是使用的 script 标签,所以只能发 get 请求,不像 post 能拿到状态码),可见他多么不好用——但是 IE 不兼容 CORS。

3. postMessage、websocket、Node 中间件代理、nginx 反向代理

这些方式能跨域,但是我们一般不会去使用。

比如,严格意义上来说 postMessage 是用来做网页之间的通信的,他的本质目的不是为了跨域,只是有时候实在没有办法了,我们可以试试 postMessage。并且他的问题也是,兼容性不强。如果我们可以使用它,那么 CORS 一般也能用了。

websocket 几乎是门新技术了,太复杂了。

其他的基本都是后端的事情,前端有前端的方式嘛。

(完)

原文地址:https://www.cnblogs.com/xhyccc/p/13330764.html