跨域

跨域关键知识:

  • 同源策略。浏览器故意设计的一个功能限制
  • CORS。突破浏览器限制的一个方法
  • JSONP。IE 时代的妥协

同源

源 = 协议 + 域名 + 端口号

window.originlocation.origin 可以得到当前源。

如果两个 url 的协议、域名、端口号完全一致,那么这两个 url 就是同源。

例:https://qq.comhttps://www.baidu.com 不同源

https://baidu.comhttps:www.baidu.com 同源

完全一致才算同源

下表给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:

URL 结果 原因
http://store.company.com/dir2/other.html 同源 只有路径不同
http://store.company.com/dir/inner/another.html 同源 只有路径不同
https://store.company.com/secure.html 失败 协议不同
http://store.company.com:81/dir/etc.html 失败 端口不同 ( http:// 默认端口是80)
http://news.company.com/dir/other.html 失败 主机不同

同源策略

Ajax 最大的限制是同源策略(Same-origin policy),它限制了不同源之间的交互,一个源的文档或脚本不能与另一个源的资源进行交互。浏览器的同源策略 MDN

  • 浏览器规定

    如果 JS 运行在源 A 里,那么就只能获取源 A 的数据

    不能获取源 B 的数据,即==不允许跨域==

  • 例如(省略 http 协议)

    假设 frank.com/index.html 引用了 cdn.com/1.js

    那么就说 1.js 运行在源 frank.com 里

    注意 这跟 cdn.com 没有关系,虽然 1.js 从它哪下载

    所以 1.js 就只能获取 frank.com 的数据

    不能获取 1.frank.com 或者 qq.com 的数据

  • 这是浏览器的功能

    浏览器故意要这样设计的

    目的:==保护用户隐私==

如果没有同源策略

以 qq 空间为例

源为 https://user.qzone.qq.com,假设,当前用户已登录(cookie),假设 AJAX 请求 /friends.json 可获取到用户好友列表。

黑客来了,假设有人给你分享 https://qzone-qq.com 给你,实际上是个钓鱼网站,你点开后,这个网页会请求你的好友列表 https://user.qzone.qq.com/friends.json。这样好友列表就能被黑客访问到。

问题根源:

  • 无法区分发送者

    qq 空间页面的 JS 和黑客网页里的 JS 发送的请求几乎没有区别(referrer 有区别)

    如果后台开发者没有检查 referrer,那么就完全没有区别

    所以,没有同源策略,任何页面都能偷 qq 空间的数据

  • 那检查 referrer 不就好了

    安全原则:安全链条的强度取决于最弱的一环

    万一这个网站的后端开发工程师就是没有检查 referrer

    所以浏览器应该主动预防这种偷数据的行为

    总之,浏览器为了用户隐私,设置了严格的同源策略

演示

  1. 创建目录

    qq-com 里新建 server.js,用来模拟 qq空间

    frank-com 里新建 server.js,用来模拟黑客网站

  2. qq-com

    public 目录下新建 index.html 首页

    qq.js 是 JS 脚本文件

    friends.json 是模拟的好友数据

    端口监听为 8888,访问 http://127.0.0.1:8888

  3. hacker-com

    public 目录下新建 index.html 首页

    frank.js 是 JS 脚本文件

    端口监听为 9999,范问 http://127.0.0.1:9999

跨域 AJAX

  • 正常使用 AJAX

    在 qq.com:8888 里运行的 JS 可以访问 /friends.json

    Snipaste_2021-09-27_01-44-31
    能够访问
  • 黑客偷数据

    在 hacker.com:9999 里运行的 JS不能访问

    浏览器需要==CORS==

Snipaste_2021-09-27_01-44-56
不能访问
  • 提问

    黑客的请求成功了没:

    答:成功了,因为 qq.com 后台有 log。

    黑客拿到响应了没与?

    答:没有,因为浏览器不给数据。

Snipaste_2021-09-27_01-53-35

如何跨域

CORS

CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)。

  • 问题根源

    浏览器默认不同源之间不能互相访问数据

    但是 qq.com 和 hacker.com 都是自己的网站,需要互相访问

  • 用 CORS

    浏览器说,如果要共享数据,需要提前声明!

    qq.com 在响应头里写 hacker.com 可以访问

    语法:Access-Control-Allow-Origin: http://hacker.com:9999

  • MDN 文档

CORS 不兼容 ie 6789

JSONP

什么是 JSONP?

跨域时,由于当前浏览器不支持 CORS 或因为某些条件不支持 CORS,我们必须使用另外一种方式来跨域。

于是,请求一个 JS 文件,这个 JS 文件会执行事先定义好的回调,这个回调里就有我们需要的数据。

优点:

  • 支持 IE

缺点:

  • 由于是 script 标签,获取不到响应状态
  • 不支持 POST

演示:hacker.com 访问 qq.com

  • qq.com 将数据写到 /friends.js
  • frank.com 用 script 标签引用 /friends.js
  • hacker.com 执行 事先定义好的 window.xxx 函数
  • /friends.js 执行 window.xxx({friend: […]})
  • 然后 hacker.com 就通过 window.xxx 获取到数据了
  • window.xxx 就是一个回调!!

JSONP 的实现原理演示:

hacker-com 里的 hacker.js

1
2
3
4
5
6
window.xxx = (data) => {
console.log(data)
}
const script = document.createElement('script');
script.src = 'http://qq.com:8888/friends.js';
document.body.appendChild(script);

qq-com public目录下新建 friends.js 内容为 window.xxx ( {{ data }} )

qq-com 的 server.js 添加如下路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else if (path === '/friends.js') {
if (request.headers['referer'].indexOf('http://hacker.com:9999') === 0) {
response.statusCode = 200
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
const string = fs.readFileSync('./public/friends.js').toString()
const data = fs.readFileSync('./public/friends.json').toString()
response.write(string.replace('{{ data }}', data))
response.end()
} else {
response.statusCode = 404;
response.end();
}
}
// 控制台 window.xxx
// hacker 拿到了 friends.js 的数据

优化:

window.xxx 能不能改其他名字?

其实名字不重要,只要 hacker.com 定义的函数名和 qq.com/friends.js 执行的函数名是同一个即可。

将名字穿给 /friends.js

1
2
3
4
5
6
7
8
9
10
11
12
// hacker.js
const random = `hackerJSONPCallback` + Math.random();
console.log(random);
window[random] = (data) => {
console.log(data)
}
const script = document.createElement('script');
script.src = `http://qq.com:8888/friends.js?functionName=${random}`;
script.onload = () => {
script.remove();
}
document.body.appendChild(script);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// server.js
else if (path === '/friends.js') {
if (request.headers['referer'].indexOf('http://hacker.com:9999') === 0) {
response.statusCode = 200
console.log(query.functionName);
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
const string = fs.readFileSync('./public/friends.js').toString()
const data = fs.readFileSync('./public/friends.json').toString()
response.write(string.replace('{{ data }}', data).replace(`{{ xxx }}`, query.functionName))
response.end()
} else {
response.statusCode = 404;
response.end();
}
}
1
2
// friends.js
window[`{{ xxx }}`]( {{ data }} )

进一步优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// hacker.js
// 封装 JSONP
function jsonp(url) {
return new Promise((resolve, reject) => {
const random = `hackerJSONPCallback` + Math.random();
console.log(random);
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('http://qq.com:8888/friends.js')
.then((data) => {
console.log(data);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// server.js
else if (path === '/friends.js') {
if (request.headers['referer'].indexOf('http://hacker.com:9999') === 0) {
response.statusCode = 200
console.log(query.functionName);
response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
const string = `window['{{ xxx }}']( {{ data }} )`
const data = fs.readFileSync('./public/friends.json').toString()
response.write(string.replace('{{ data }}', data).replace(`{{ xxx }}`, query.callback))
response.end()
} else {
response.statusCode = 404;
response.end();
}
}

JSONP 的本质是前后端的协作,即前端把想要的资源以及后续的处理都告诉后台,后台封装好返回给前端执行。

优质博客:

不要再问我跨域的问题了

同源策略于JS跨域(JSONP,CORD)