同源策略和跨域通信

什么是同源策略? 同源策略(Same Origin Policy)是一种约定,它是浏览器最核心也最基本的安全功能。 如果缺少了同源策略,则浏览器的正常功能可能会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一

目录
  1. 同源策略
    1. 定义
    2. 目的
    3. 限制行为
  2. 跨域
    1. 定义
    2. 产生
  3. 跨域通信
    1. JSONP
    2. CORS
    3. WebSocket
    4. postMessage
  4. 总结

同源策略#

定义#

什么是同源策略?

同源策略(Same Origin Policy)是一种约定,它是浏览器最核心也最基本的安全功能。

如果缺少了同源策略,则浏览器的正常功能可能会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

image.png

同源即是指协议、域名、与端口都相同,

不同源的客户端脚本(JavaScript、ActionScript)在没明确授权的情况下,不能读写对方的资源。

目的#

为了保证用户信息的安全,防止恶意的网站窃取数据

限制行为#

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 无法获得
  • AJAX 请求不能发送

跨域#

定义#

跨域,顾名思义,即是跨越、突破浏览器的同源限制,不同域之间相互请求资源

产生#

因为有同源策略的限制,一般情况下,不同域之间是无法通信。但是有时候我们不得不突破同源策略的限制,在不同的域之间进行资源请求。

一个简单的例子:

我的一个服务器(服务器 A)上空间容量有限且不能扩充,在上面存放了一些资源后,想要再存放一些图片已经放不下去了,没方法只好把图片放在另外一个服务器上(服务器 B)了。某个时候因为某个原因,服务器 A 的一个网页需要引用一张图片,但是这个图片在服务器 B 上,怎么办呢?因为两个服务器的协议,IP 地址都是不一样的,根据浏览器的同源策略限制,它们之间是无法访问的呀?

怎么办呢?这个时候跨域通信就产生了。

如果经常浏览网页,我们会发现大部分网站上页面和图片是分开放在不同的服务器上的,然后进行跨域请求资源。

大家还记得吗? HTML 中有个 src 属性就是支持跨域通信的,一些标签比如 img,script 都有这个属性, 可以跨域引用资源。

另外,link 标签的 href 属性也支持跨域。

<img src="xxx" />
<script src="xxx"></script>
<link rel="stylesheet" href="xxx" />

跨域打破了浏览器的同源策略,可能会产生一些安全的问题。所以,所有的跨域请求都必须经过信息提供方允许才能进行,如果未经允许就可跨域获取资源,那么一定是同源策略出现了漏洞。

跨域通信#

理解了同源策略和跨域后,下面讲一下跨域通信的几种常用方法。

JSONP#

定义

JSONP 利用 script 标签的 src 属性可以跨域的特性,动态创建一个 script 标签,

标签引用跨域服务器的一段 js 代码,这段代码是一个回调函数,回调函数里面的参数放入所需的数据或者资源,

因为回调函数的名字和本地的函数名字一样,所以引用这段代码后就直接调用了本地的函数,输出内容。

代码示例

本地代码文件:local.html

<script>
  var localHandler = function (data) {
    alert(data);
  };
</script>
<script scr="跨域服务器/remote.js"></script>

跨域服务器代码文件:remote.js

localHandler(`哈哈,我是参数也是你需要的data!`);

上面是一个最简单的 jsonp 代码示例了。也许有人会问:一般服务器的数据会有很多的跨域访问,

怎么让远程服务器知道你要调用的本地函数名称是什么呀?其实 JSONP 跨域请求一般是需要传一个 callback 函数给服务器的。

场景实例

实例场景:李明同学想要查询期末考试的成绩,他的学号是 030206。

本地代码文件:local.html

<script>
  // 创建本地函数
  var searchScore = function (data) {
    alert(
      data.class +
        data.name +
        "同学的成绩是:语文:" +
        data.score.chinese +
        "; 数学:" +
        data.score.math +
        "。"
    );
  };
  // 需要提供服务的url,传入id和callback函数
  var url = "跨域服务器/score.php?id=030206&callback=searchScore";
 
  // 新建一个script标签
  var script = document.createElement("script");
 
  // 设置script标签的src值为url
  script.setAttribute("src", url);
 
  // 把新建的script标签添加到在head标签上
  document.getElementsByTagName("head")[0].appendChild(script);
</script>

上面代码相当于在 head 标签下面生成了一个 script 标签

<head>
  <script src="跨域服务器/score.php?id=030205&callback=searchScore"></script>
</head>

服务器会根据这个 url,就会返回一段 js 代码,代码如下:

searchScore({
  class: "三年级二班",
  name: "李明",
  score: {
    chinese: 80,
    math: 85,
  },
});

然后这段代码就会执行上一个 script 代码中的searchScore()这个函数,页面弹出警告框:

三年级二班李明同学的成绩是:语文:80。数学:85。

至于服务器的输出是怎么实现的呢,这里说下原理。

服务器接受到 id 和 callback 后,会先通过 id 查询到这个同学的成绩,然后用把查询到的成绩和 callback 函数名(回调函数)拼接成一段 js 代码传出去。服务器实现的功能就是查询和拼接字符串代码,然后把代码return出去。

CORS#

CORS 全称是"跨域资源共享"(Cross-origin resource sharing),是一个 W3C 的标准。它允许XMLHttpRequest跨域请求,从而克服了 AJAX 只能同源使用的限制。

CORS 只需要在服务器端设置即可,客户端不需要设置。客户端只需要支持 CORS 这个功能就可以了,目前 IE10 以上的浏览器和现代主流浏览器都支持。

服务器代码如下, 不同后端语言写法可能不一样:

// 必需,值若为 * 而为支持所有的域名请求,不建议这样写
response.setHeader("Access-Control-Allow-Origin", "http://a.com, http://b.com");
// 如果浏览器有Access-Control-Request-Headers字段,则该字段必需
response.setHeader("Access-Control-Allow-Header", "X-Requested-With");
// 必需,支持请求的方法
response.setHeader(
  "Access-Control-Allow-Methods",
  "PUT,POST,GET,DELETE,OPTIONS"
);
// 可选,接受跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
// ... 其他设置

PS:django 可以下载一个第三方库django-cors-headers,然后配置下就可以轻松提供跨域服务了。

WebSocket#

WebSocket 是一种通信协议,该协议不实行同源策略,只要服务器支持就可以实现跨域通信。

浏览器发出的 WebSocket 请求头信息中,会带有一个 Origin 字段,表示该请求的请求源。

Origin: http://www.example.com

正是因为有了这个字段,WebSocket 才不实行同源策略。服务器会根据这个字段,判断是否允许本次通信。如果服务器的白名单中有这个域名,就可以实现跨域通信。

postMessage#

HTML5 新增 postMessage 方法,postMessage 可以实现跨文档消息传输。

兼容性:IE8 以上浏览器和现在的主流浏览器都支持。

postMessage 方法是通过绑定 window 的 message 事件来监听发送跨文档消息传输内容。

代码示例:

// 窗口A(http://A.com)向跨域的窗口B(http://B.com)发送信息
BWindow.postMessage("data", "http://B.com");
// 在窗口B中监听message事件
AWindow.addEventListener(
  "message",
  function (event) {
    console.log(event.origin); // http://A.com 消息源的URI
    console.log(event.source); // AWindow 消息源
    console.log(event.data); // data 数据
  },
  false
);

如果是窗口 B 向窗口 A 发送消息呢,反过来原理也是一样的。

总结#

跨域通信用得最多的就是 JSONP 和 CORS 了,JSONP 只支持 GET 请求,CORS 允许任何类型的请求。

不过 JSONP 的支持会更好一些,几乎所有的浏览器都支持。