跨域

什么是跨域

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 JavaScript 实施的安全限制。

所谓的同源是指,域名、协议、端口均为相同。

同源策略限制了一下行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 和 JS 对象无法获取
  • Ajax 请求发送不出去

实际代码

  1. 创建 index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require("express");

// 以当前路径创建服务,默认加载当前目录index.html
const app = express();
app.use(express.static(__dirname));
app.listen(90);

// 以路由创建服务
const app2 = express();
app2.get("/", function (req, res) {
res.send("你好");
});
app2.listen(91);
  1. 在当前目录创建 index.html
1
2
3
4
5
6
<!DOCTYPE html>
<html lang="en">
<body>
hello
</body>
</html>
  1. 启动服务node index.js. 可以访问http://localhost:90/http://localhost:91/. 这两个服务端口不同,不满足同源策略,因此互相访问受限制

  2. 在 index.html 中尝试访问 http://localhost:91/, 增加下面代码

1
2
3
4
<script>
fetch("http://localhost:91/") .then((res) => res.text()) .then((data) =>
alert(data));
</script>

可以看到会出现“CORS”相关的报错:

如何解决跨域

jsonp 跨域

JSONP(JSON with Padding)是 JSON 的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com 的服务器沟通,而 HTML 的 <script> 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的 JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

实现方式

  1. 修改 index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const express = require("express");

// 以当前路径创建服务,默认加载当前目录index.html
const app = express();
app.use(express.static(__dirname));
app.listen(90);

// 以路由创建服务
const app2 = express();
app2.get("/", function (req, res) {
const callbackName = req.query.callback;
res.send(callbackName + "('你好')");
});
app2.listen(91);

可以看到 91 端口的服务接收一个 callback 的参数,然后与返回的数据一起拼成一段 js 代码:callback(data)

  1. 修改 index.html
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<body>
hello
</body>
<script>
function f(data) {
alert(data);
}
</script>
<script src="http://localhost:91?callback=f"></script>
</html>

script 标签不受同源策略的限制,因此我们通过 script 标签发送请求

查看控制台,可以看到请求回来一段 js 代码

CORS 跨域

CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现 CORS 通信的关键是服务器。 只要服务器实现了 CORS 接口 ,就可以跨源通信。

  1. 我们还原 index.html
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<body>
hello
</body>
<script>
fetch("http://localhost:91/")
.then((res) => res.text())
.then((data) => alert(data));
</script>
</html>
  1. 修改 index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const express = require("express");

// 以当前路径创建服务,默认加载当前目录index.html
const app = express();
app.use(express.static(__dirname));
app.listen(90);

// 以路由创建服务
const app2 = express();
app2.get("/", function (req, res) {
// 增加header配置
res.header("Access-Control-Allow-Origin", "*");
res.send("你好");
});
app2.listen(91);

优缺点

  • JSONP 的主要优势在于对浏览器的支持较好;虽然目前主流浏览器支持 CORS,但 IE10 以下不支持 CORS。
  • JSONP 只能用于获取资源(即只读,类似于 GET 请求);CORS 支持所有类型的 HTTP 请求,功能完善。(这点 JSONP 被玩虐,但大部分情况下 GET 已经能满足需求了)
  • JSONP 的错误处理机制并不完善,我们没办法进行错误处理;而 CORS 可以通过 onerror 事件监听错误,并且浏览器控制台会看到报错信息,利于排查。
  • JSONP 只会发一次请求;而对于复杂请求,CORS 会发两次请求。
  • 始终觉得安全性这个东西是相对的,没有绝对的安全,也做不到绝对的安全。毕竟 JSONP 并不是跨域规范,它存在很明显的安全问题:callback 参数注入和资源访问授权设置。CORS 好歹也算是个跨域规范,在资源访问授权方面进行了限制(Access-Control-Allow-Origin),而且标准浏览器都做了安全限制,比如拒绝手动设置 origin 字段,相对来说是安全了一点。
    但是回过头来看一下,就算是不安全的 JSONP,我们依然可以在服务端端进行一些权限的限制,服务端和客户端也都依然可以做一些注入的安全处理,哪怕被攻克,它也只能读一些东西。就算是比较安全的 CORS,同样可以在服务端设置出现漏洞或者不在浏览器的跨域限制环境下进行攻击,而且它不仅可以读,还可以写。