多种跨域方案详解

同源策略

同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以 xyz.com 下的 js 脚本采用 ajax 读取 abc.com 里面的文件数据是会被拒绝的。

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

所谓同源是指:域名、协议、端口相同。

网址 结果 原因
https://yuwangi.github.io 成功 域名、协议、端口均相同
http://yuwangi.github.io 失败 协议不同
https://yuwangi.github.io:9001 失败 端口不同
https://smwang.github.io 失败 域名不同

为什么要有跨域限制

因为存在浏览器同源策略,所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到,跨域限制主要的目的就是为了用户的上网安全。

如果浏览器没有同源策略,会存在什么样的安全问题呢。下面从 DOM 同源策略和 XMLHttpRequest 同源策略来举例说明:

如果没有 DOM 同源策略,也就是说不同域的 iframe 之间可以相互访问,那么黑客可以这样进行攻击:

做一个假网站,里面用 iframe 嵌套一个银行网站 http://mybank.com
iframe 宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
这时如果用户输入账号密码,我们的主网站可以跨域访问到 http://mybank.comdom 节点,就可以拿到用户的账户密码了。
如果 XMLHttpRequest 同源策略,那么黑客可以进行 CSRF(跨站请求伪造) 攻击:

用户登录了自己的银行页面 http://mybank.com,http://mybank.com 向用户的 cookie 中添加用户标识。
用户浏览了恶意页面 http://evil.com,执行了页面中的恶意 AJAX 请求代码。
http://evil.comhttp://mybank.com 发起 AJAX HTTP 请求,请求会默认把 http://mybank.com 对应 cookie 也同时发送过去。
银行页面从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据。此时数据就泄露了。
而且由于 Ajax 在后台执行,用户无法感知这一过程。
因此,有了浏览器同源策略,我们才能更安全的上网。

跨域解决方案

  • jsonp
  • cors
  • postMessage
  • document.domain
  • window.name
  • location.hash
  • http-proxy
  • nginx
  • websocket

jsonp

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
26
27
28
29
30
31
32
//此处直接手写一个jsonp
function jsonp({ url, params, cb }) {
return new Promise((resolve, reject) => {
let script = document.createElement("script");
window[cb] = function(data) {
resolve(data);
document.body.removeChild(script);
};
params = {
...params,
cb
};
let arrs = [];
for (let key in params) {
arrs.push(`${key}=${params[key]}`);
}
script.src = `${url}?${arrs.join("&")}`;
document.body.appendChild(script);
});
}
//只能发送get请求 不支持post put delect
//不安全 xss攻击
jsonp({
url: "https://image.baidu.com/httpsjsonp/pc", //此处链接为百度搜索随便找的
params: {
callback: "imageCheckHttps",
_: 1571021917647
},
cb: "imageCheckHttps"
}).then(data => {
console.log(data);
});

1564156

cors

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
26
27
28
//index.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
Hello
<script>
let xhr = new XMLHttpRequest;
xhr.open('GET', 'http://localhost:3001/getData', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
console.log(xhr.response)
}
}
}
xhr.send();
</script>
</body>

</html>
1
2
3
4
5
6
//server.js
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//server2.js
const express = require("express");
let app = express();
let whiteList = ["http://localhost:3000"];
app.use(function(req, res, next) {
console.log(req.headers);
let origin = req.headers.origin;
if (whiteList.includes(origin)) {
//设置源 请求头
res.setHeader("Access-Control-Allow-Origin", origin);
}
next();
});
app.get("/getData", function(req, res, next) {
console.log("req.headers", "111");
res.end("我不爱你");
});
app.use(express.static(__dirname));
app.listen(3001);

165156
1665165165

postMessage

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
//a.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
<iframe id="frame" src="http://localhost:3001/b.html" frameborder="0" onload="load()"></iframe>
</body>
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:3001')
window.onmessage = function (e) {
console.log(e.data)
}
}
</script>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//b.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
<script>
window.onmessage = function (e) {
console.log(e);
console.log(e.data)
e.source.postMessage('我不爱你', e.origin)
}
</script>
</body>

</html>
1
2
3
4
5
6
// a server.js
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);
1
2
3
4
5
6
// b server.js
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);

4694165416

document.domain

对于主域名相同,且协议,端口一致,而子域名不同的情况,可以使用 document.domain 来跨域。

1
2
3
4
5
//一级域名、二级域名

//一级域名
//iframe 引用二级域名
//只需在两个域名设置 document.domain 为同一值 即可

window.name

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
26
27
28
29
30
31
32
33
34
//a.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
//a和b是同域的 localhost:3000
//c 是独立的 localhost:3001
//a 获取c的值
//a先引用c c把值放到window.name 然后把a引用的地址改为b

<iframe id="iframe" src="http://localhost:3001/c.html" onload="load()" frameborder="0"></iframe>
<script>
let first = true;

function load() {
let iframe = document.getElementById('iframe');
if (first) {
iframe.src = 'http://localhost:3000/b.html';
first = false;
} else {
console.log(iframe.contentWindow.name)
}
}
</script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//b.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>

</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//c.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
<script>
Window.name = '我不爱你'
</script>
</body>

</html>
1
2
3
4
5
6
// server a
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);
1
2
3
4
5
6
// server b
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3001);

location.hash

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
//a.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
<iframe src="http://localhost:3001/c.html#iloveyou" frameborder="0"></iframe>
</body>
<script>
//路径后的hash是可以通信的
//目的:a想访问c的数据
// a给c传一个hash值 => c收到后 把hash值传递给b => b将结果放到a的hash值中
// a、b 同域 c不同域
window.onhashchange = function () {
console.log(location.hash)
}
</script>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//b.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>

</body>
<script>
window.parent.parent.location.hash = location.hash;
</script>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//c.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>

</body>
<script>
console.log(location.hash);
let iframe = document.createElement('iframe');
iframe.src = 'http://localhost:3000/b.html#idontloveyou';
document.body.appendChild(iframe);
</script>

</html>
1
2
3
4
5
6
// a server.js
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);
1
2
3
4
5
6
// b server.js
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3001);

46465415645

http-proxy

类似于 webpack 转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
devServer: {
host: '0.0.0.0',
port: 9001,
headers: {
'Access-Control-Allow-Origin': '*'
},
historyApiFallback: {
rewrites: [{
from: /.*/g,
to: '/www/view/index.html'
}]
},
proxy: {
'/api': {
target: 'http://192.168.1.192:8091/'
}
}
}

nginx

首先需要先下载 nginx, http://nginx.org/en/download.html

1
2
3
4
5
//配置nginx
location ~.*\.json {
root json;//转发文件放在 json文件夹下
add_header "Access-Control-Allow-Origin" "*";
}
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
26
27
28
//index.html
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
<script>
let xhr = new XMLHttpRequest;
xhr.open('GET', 'http://localhost:3001/getData', true);
xhr.setRequestHeader('Access-Control-Allow-Origin', 'http://localhost:3001')
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
console.log(xhr.response)
}
}
}
xhr.send();
</script>
</body>

</html>
1
2
3
4
5
6
//server.js
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);

websocket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
<script>
//高阶api
let socket = new WebSocket('ws://localhost:3000');
socket.onopen = function () {
socket.send('我爱你')
}
socket.onmessage = function (data) {
console.log(data.data)
}
</script>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
//server.js
const express = require("express");
let Websocket = require("ws");
let app = express();
let wss = new Websocket.Server({ port: 3000 });
wss.on("connection", function(ws) {
ws.on("message", function(data) {
console.log(data);
ws.send("我不爱你");
});
});

4646416416

494894898489