跨域获取同源资源
AJAX 技术使开发者能够专注于互联网中数据的传输,而不再拘泥于数据传输的载体。通过它,我们获取数据的方式变得更加灵活,可控和优雅。但 AJAX 技术并不是一把万能钥匙,互联网中的数据隐私和数据安全(例如银行账号和密码)非常重要,为了保护用户数据的隐私与安全,浏览器使用同源策略限制了 AJAX 技术获取数据的范围和能力。但有时我们不得不想办法绕过同源策略,实现跨域请求资源。因此跨域技术一直成为开发者们经久不衰的讨论话题。
一、同源策略
互联网的数据要么存储在服务端(即服务器,如数据库、硬盘等)中,要么存储在客户端(即浏览器,如 cookie,localStorage,sessionStorage)中。互联网数据的传输实际上就是客户端与服务端之间的交互。而所谓的数据隐私,说白了就是数据拥有者对数据索取者发出警告:“不是你的你别动”。如果你在客户端,想要获取服务端数据,首先需要通过服务器端的验证,证明你有权限获取数据;而如果你在服务端,想要获取客户端的某些数据,同样需要客户端通过某些方式验证你有资格获取相应的数据资源。
浏览器的同源策略是指:限制不同源之间执行特定操作。
① 一个源由协议,域名和端口三部分组成,这三者任意一个不同都会被浏览器识别为不同的源;
多数情况下,不同的源意味着它们在互联网中归属于不同的站点(或是不同的用途)。因此,它们可能是不同的项目,有不同的文件根目录,那么它们的数据自然不能共享,否则数据隐私和安全无法保障。
② 上文所提到的特定操作是指:
- 读取 Cookie,LocalStorage 和 IndexDB;
- 获取 DOM 元素;
- 发送 AJAX 请求;
假如浏览器允许我们执行这些特定操作,那会带来如下风险。cookie 原理
首先,由于很多网站使用浏览器存储用户的账号和密码,那么便可以在 A 域中(在服务器上托管的网站)读取任意来访用户的所有 Cookie 信息(没有同源策略的保护,该用户所有网站的 Cookie 记录都是透明的),然后就能利用这些 Cookie 信息伪装成来访用户做任何事。正是出于同源政策的保护,我们只能访问用户该域下的 Cookie 信息,即服务端自己设置的 Cookie 信息。
其次,如果能够获取不同域下的 DOM 元素,我们就可以通过<iframe>
标签在 A 域网站上引入 B 域网站,然后诱使用户在 B 域网站操作,由于我们能够跨域获取 DOM 元素,因此我们可以操作 B 域网站的 DOM 结构,用户输入的一切信息,以及用户操作的 DOM 元素都会被泄露。这正是同源策略想要规避的安全隐患。
当设置 Cookie 时,除了存放键值对形式的数据信息外,浏览器还会为 Cookie 的一些属性填充默认值(可手动修改属性值)。在这些属性中,domain 代表域名,path 代表路径,两者构成了一个确定这条 Cookie 何时被调用和访问的 URL。与此同时,浏览器自己维护的 Cookie 文件中也会添加这一条新创建的 Cookie 数据。当在浏览器中发送 HTTP 请求时,浏览器首先会检查请求地址并在自己所维护的 Cookie 文件中寻找匹配的 Cookie 信息,将其添加到请求头中的Cookie
属性内,然后向服务器发送请求。这个自动添加相应 Cookie 信息的过程是浏览器偷偷做的,即我们无法控制这个过程。当 HTTP 请求到达服务器时,服务器返回的响应中,响应头会原封不动返回浏览器发送给它的 Cookie 信息。我们虽然不能在发送请求前获得 Cookie 信息,但在发送请求后还是能够获得用户的 Cookie!
再进一步解释一下这和 AJAX 的关系,假设我们在自己的服务器上托管了站点 A,并在其中隐藏了一段脚本,每个登录站点 A 的人都会自动发送 AJAX 请求至站点 B(假设站点 B 是一个银行),那么在没有浏览器同源策略的情况下,如果站点 A 中的访问者恰好有 Cookie 中保留站点 B 信息的用户,通过 AJAX 请求返回的响应头,我们一样可以拿到这位用户的站点 B Cookie,从而伪装成用户在站点 B 登录,做一些违法乱纪的事情(CSRF攻击即是利用了这个原理,只不过出于同源策略限制,并不能通过发起 AJAX 的方式)这也是为什么要禁止不同源的站点发送 AJAX 请求。https://segmentfault.com/a/1190000004556040)
在浏览器同源策略的限制下,获取跨域 Cookie,DOM 结构和发送 AJAX 时的真实情况会是如下:
首先,我们在一个域下只能读取该域下的 Cookie 值。当我们在页面中使用<iframe>
标签时,我们获取对应 DOM 节点下只有一个空空的#document
节点,并没有额外的 DOM 信息。

而对于 AJAX,浏览器其实并没有阻止我们向不同域发送请求,其阻止的是这次请求的响应,也就是说服务端其实接收到了这次请求,只是响应被浏览器解析时被浏览器发现违背了同源策略而被拒绝,此时浏览器会在控制台中打印出一条错误信息。
另外对于 XHR 请求,实际上在请求报头也不会看到相应 Cookie 信息,因为 CORS 标准中规定默认情况下,浏览器在发送跨域请求时不能发送任何认证信息,比如cookies
和HTTP authentication schemes
。除非你显式地将xhr
实例的withCredentials
属性的值设置为true
并且服务器端也允许客户端请求携带认证信息(即服务器端在响应头中设置了Access-Control-Allow-Credentials: true
)。
二、跨域请求资源方案
当我们拥有多个站点,并且这些站点又经常共享相同的数据,那么为每个站点存储一份数据看起来就蠢透了。更好的方案是,我们建设一台静态资源存储服务器,然后所有站点都从这一台服务器上获取资源。很理想的方案,但是现实中首要解决的问题便是浏览器的同源策略,不同域之间无法通过 AJAX 技术获取资源。这是需要跨域获取资源的主要情景。
无论是怎样的跨域资源获取方案,本质上都需要服务器端的支持。跨域获取资源之所以能够成功,本质是服务器默许了你有权限获取相应资源。下面所运用的种种方式,实际上是客户端和服务端互相配合,绕过同源策略进行数据交互的工作。
1. JSONP
正如标题所描述的那样,JSONP 技术是早期的跨域资源获取方式,由于该技术的简单易用,逐渐变得流行,最终成为经典的跨域获取资源方案。JSONP 是“JSON with padding”的简写,我将其翻译为“被包裹的 JSON”。
首先,浏览器的同源策略只是阻止了通过 AJAX 技术跨域获取资源,而没有禁止跨域获取资源这件事本身,因此可以通过<link>
标签href
属性或<img>
标签以及<script>
标签中的src
属性获取异域的 CSS,JS 资源和图片(其实并不能读取这些资源的内容);其次,<script>
标签通过src
属性加载的 JS 资源,实际上只是将 JS 文件内容原封不动放置在<scritp>
的标签内。
也就是说,如果 sayHi.js 文件只有这样一段代码:
1 | // sayHi.js |
当我们在 HTML 文件中,成功加载 sayHi.js 文件时,浏览器只不过是做了如下操作:
1 | <!-- 加载前 --> |
这意味着被加载的文件与 HTML 文件下的其他 JS 文件共享一个全局作用域。也就是说,<scritp>
标签加载到的资源是可以被全局作用域下的函数所使用。但如果<script>
标签加载到的一些数据并不符合 JavaScript 语法规定的数据类型,JavaScript 就无法处理这些错误,而且就算数据类型正常了,我们还应该将数据存储于一个变量内,然后调用这个变量。
但我们已经约定好了数据的格式为 JSON,这是 JavaScript 可以处理的数据类型,并且 JSON 格式的数据可以承载大量信息。那么至于变量问题,我们则会通过向服务器传入一个函数的方式,将数据变为函数的参数,让我们直接看看 JSONP 的使用方式:
1 | function handleResponse(response) { |
很容易看到,我们在 1-3 行中创建了一个函数,该函数用来处理我们将要获得的数据,该函数的参数response
即是服务器响应的数据。在 4-6 行中我们所做的是利用 JavaScript 动态生成一个 script 标签,并将其插入 HTML 文档。但是注意第 5 行我们制定的 src 值,在 URL 末尾,我们有这样一段查询参数callback=handleResponse
,callback 的值正是我们先前创建的函数。
事情开始变得有些令人困惑了,究竟发生了什么呢?我们如何通过上述代码最终实现跨域获取资源?
答案就藏在服务端的代码中,当服务端支持 JSONP 技术时,会做如下一些设置:
- 识别请求的 URL,提取 callback 参数的值,并动态生成一个执行该参数值(一个函数)的 JavaScript 语句;
- 将需要返回的数据放入动态生成的函数中,等待其加在到页面时被执行;
此时该文件内容看起来就像这样:
1 | handleResponse(response) // response 为被请求的 JSON 格式的数据 |
因此,当资源加载到位,内容显示在 script 标签内时,浏览器引擎会执行这条语句,我们想要的数据就可以以任何想要的方式处理了。你现在知道为什么这项技术被命名为 JSONP 了吧?那个“padding”指的就是我们的“callback”函数,真是恰如其名。
最后,我们还要对 JSONP 技术再强调两点:
- JSONP 技术与 AJAX 技术无关:虽然同样牵扯到跨域获取资源这个主题,但 JSONP 的本质是绕过 AJAX 获取资源的机制,使用原始的
src
属性获取异域资源; - JSONP 技术存在三点缺陷:
- 无法发送 POST 请求,也就是说 JSONP 技术只能用于请求异域资源,无法上传数据或修改异域数据;
- 无法监测 JSONP 请求是否失败;
- 可能存在安全隐患:JSONP 之所以能成功获取异域服务器资源,靠的是服务器动态生成了回调函数,并在页面中执行,那么如果服务器在原有的回调函数下再添加些别的恶意 JavaScript 代码也会被执行!所以在使用 JSONP 技术时,一定要确保请求资源的服务器是值得信赖的;
虽然存在一些缺陷,但 JSONP 的浏览器兼容性却是非常好的,可以说是一种非常小巧高效的跨域资源获取技术。
2. CORS
CORS 是 W3C 颁布的一个浏览器技术规范,其全称为“跨域资源共享”(Cross-origin resource sharing),它是由 W3C 官方推广的允许通过 AJAX 技术跨域获取资源的规范,因此相较于 JSONP 而言,功能更加强大,使用起来也没有了 hack 的味道。
关于 CORS 的具体细节,建议移步阮一峰的同主题博客阅读。
如果想要绕过浏览器同源策略,通过 AJAX 技术跨域获取资源,这需要服务端和客户端的协同合作。而对于 CORS 标准而言,实现 AJAX 跨域获取资源,重点在于服务器端返回的响应是否清楚告知了浏览器此次跨域 AJAX 请求的合法性。
根据 AJAX 请求的复杂程度不同,服务器要向浏览器做出的说明程度也不同。
简单的 AJAX 请求
- 请求方法只属于HEAD,GET,POST请求的其中一种;
- HTTP 的头信息只限于以下字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type(只能为
application/x-www-form-urlencoded
,multipart/form-data
和text/plain
其中一种)
而当浏览器检测到一个简单的跨域 AJAX 请求,浏览器会首先为我们添加一个头部信息:Origin
。它的值为请求方所在的源。
1 | GET /cors HTTP/1.1 |
而当这样的一条 HTTP 请求发送到服务端时,服务端会检测该请求报头中的Origin
字段的值是否在许可范围内,如果的确是服务端认可的域,那么服务端会在响应报文中添加如下字段:
Access-Control-Allow-Origin
(必须):该字段用来告知浏览器服务端接受的能够发送跨域 AJAX 请求的域,它的值要么是该次 AJAX 请求报头中由浏览器自动添加的Origin
值,要么是一个*
号,表示可以接受任意的域名请求;Access-Control-Allow-Credentials
(可选):该字段用来告知浏览器是否允许客户端向服务端发送 Cookie。默认情况下,CORS 规范会阻止跨域 AJAX 向服务端发送 Cookie,因此该字段默认值为false
,当你显式的将该字段值设置为true
时,则表示允许此次跨域 AJAX 向服务端发送 Cookie;Access-Control-Expose-Headers
(可选):该字段用来向客户端暴露可获取的响应头。
CORS 规范规定,客户端XMLHttpRequest
对象的getResponseHeader()
方法只能拿到 6 个基本字段:
Cache-Control
:表示响应遵循的缓存机制;Content-Language
:表示响应体的语言;Content-Type
:表示响应体的 MIME 类型;Expires
:表示文档的过期时间,到期不再缓存;Last-Modified
:表示文档的最后改动时间;Pragma
:用来包含特定的指令。如果客户端想获取额外的响应头字段时,就需要服务端通过在该字段后定义相应的客户端可获取的响应头字段名称。
CORS 规范中为什么默认不允许跨域 AJAX 请求携带 Cookie?
在客户端与服务端数据传输的过程中,Cookie 一直是以明文的形式伴随着数据的传输,只要客户端发送了 Cookie 到服务端,服务端就会至少返回该段 Cookie。而大多数网站都使用 Cookie 短暂存储用户会话中的身份信息,因此将 Cookie 暴露在外是存在安全隐患的,CSRF 攻击的目的便是获取用户的 Cookie 信息,因此在跨域 AJAX 请求中,为了减少 Cookie 泄露的风险,CORS 规范默认禁止跨域 AJAX 请求携带 Cookie。
如果通过客户端与服务端相互配合,使得客户端能够携带 Cookie 信息?
Ⅰ. 客户端:开发者需要在创建 XMLHttpRequest 对象实例时,手动配置
withCredentials
属性,将其值设置为true
:
1
2 var xhr = new XMLHttpRequest()
xhr.withCredentials = true某些浏览器会默认允许在跨域 AJAX 请求中发送 Cookie,此时如果不想要发送 Cookie,只需要将其值设置为
false
。Ⅱ. 服务端:对于服务端而言,除了要在响应报头设置
Access-Control-Allow-Credential
字段的值为true
之外,为了保护客户端与服务端 Cookie 的隐私和安全,还需要为Access-Control-Allow-Origin
字段设置一个明确的域,不可以再使用*
号。
复杂的 AJAX 请求
与简单 AJAX 跨域请求不同,复杂的 AJAX 跨域请求一共会发送两次 HTTP 请求,其中第一次为查询请求,第二次才是正式的AJAX 跨域请求。当发送复杂的 AJAX 跨域请求时,浏览器拿到请求开始识别,然后发现这个请求不满足简单跨域 AJAX 请求标准,于是会询问服务端是否允许异域的客户端向它发送额外的请求信息,这即是第一次 HTTP 请求(查询请求)。而服务端会给出相应的回答,然后浏览器就会根据回答的结果决定是否继续发送该跨域 AJAX 请求。
一个复杂的 AJAX 跨域请求如下:
1 | var url = 'http://another.com/cors' |
当浏览器识别到该请求不简单时,就会自动向服务器发送一个查询请求,其报头信息大致如下:
1 | OPTIONS /cors HTTP/1.1 |
注意这次查询请求使用了 OPTIONS 的请求方法,表明了这是一个查询请求。请求头部的信息说明了请求来源的域,请求使用的 HTTP 方法以及请求额外发送的头部字段。
当服务端接收到浏览器发来的查询请求后,如果允许接收客户端的请求,则会返回浏览器如下的响应报文:
1 | HTTP/1.1 200 OK |
Access-Control-Allow-Origin
:向浏览器说明了发起 AJAX 请求的域是被服务器认可的(字段值也可以为一个“*”号);Access-Control-Allow-Methods
:说明了服务器接收跨域 AJAX 的请求方式;Access-Control-Allow-Headers
:说明了服务器允许跨域 AJAX 额外发送的报头信息;
当浏览器收到服务端同意请求的响应后,就会正常发送接下来的跨域 AJAX 请求,而服务器也会正常回应。在服务端与客户端整个跨域 AJAX 请求的交互中,Access-Control-Allow-Origin
头信息自始至终都是必须携带的。
而如果服务器在收到查询请求后不同意该请求,则会返回一个正常的 HTTP 响应,报文中包含任何与 CORS 规范有关的报头字段,用来表示服务器拒绝接收客户端的跨域 AJAX 请求,因此浏览器会返回一个错误状态(可以被 XML 对象实例使用 onerror 回调函数捕获)并在控制台打印一条错误信息:
1 | XMLHttpRequest cannot load http://another.com |
对于复杂的跨域 AJAX 请求,浏览器会向服务器发送两次 HTTP 请求,但如果能够一次搞定,就无需每次请求都重复两次。对于服务器而言,一次搞定的方法就在于,在浏览器第一次发送复杂的跨域 AJAX 查询请求时,在响应报头中添加Access-Control-Max-Age
字段,这是一个可选的字段,它用来指定本次查询请求的有效期,单位为秒。通过该字段,服务器拥有了告知浏览器“这个请求我准了,X 秒以内不需要再向我确认”的能力。至此,接下来的跨域请求数由两次节约为一次。
3. WebSocket
WebSocket 是一种在单个 TCP 连接上进行全双工通讯的协议。HTML5 标准之所以提出了这种新的互联网通信协议,是为了弥补在服务端与客户端的双向通信时使用 HTTP 协议通信的一些不足。但这并不意味 WebSocket 协议可以完全取代 HTTP 协议,两者都有各自擅长的领域,时不时还能一同协作解决难题。
当我们使用 HTTP 协议时,客户端与服务端的通信模式始终是由客户端向服务端发送请求,服务端只负责验证请求并返回响应。而客户端发送的每一个请求,对于服务端而言都是全新的,也即是说 HTTP 协议是无状态的。乍看似乎不合理,但这种设计却使服务器的工作变得简单可控,提升了服务器的工作效率。
但这样的设计仍然存在两个问题:
- 每一个请求都需要身份验证,这对于用户而言意味着需要在每一次发送请求时输入身份信息;
- 当客户端所请求的资源是动态生成时,客户端无法在资源生成时得到通知;
对于前者,可以使用 Cookie 解决,而对于后者,则轮到 WebSocket 大显身手。在讨论 WebSocket 之前,让我们先稍微绕点路,谈谈“Cookie”是如何解决“每一个请求都需要身份验证”的问题的。
Cookie:为 HTTP 协议添加状态
HTTP 协议下,客户端与服务端的通信是无状态的,即如果服务器中的某部分资源是由特定客户专属的,那么每当这个客户想要获取资源时,都需要先在浏览器中输入账号密码,然后再发送请求,并在被服务器识别身份信息成功后获取请求的资源。为了避免这般繁琐的操作,我们引入了 Cookie:它既可以存储在浏览器,又会被浏览器发送 HTTP 请求时默认发送至服务端,并且还受浏览器同源策略保护,帮助我们提高发起一次请求的效率。
在有了 Cookie 后,我们可以在一次会话中(从用户登录到浏览器关闭)只输入一次账号密码,然后将其保存在 Cookie 中,在整个会话期间,Cookie 都会伴随着 HTTP 请求的发送被服务器识别,从而避免了重复输入身份信息。
而且基于 Cookie 可以保存在浏览器内并在浏览器发送 HTTP 请求时默认携带的特性,服务端也可以操作 Cookie。Cookie 还可以帮助我们节省网络请求的发起数量。例如,在制作一个购物网站时,我们不希望用户在每添加一个商品到购物车就向服务器发送一个请求(请求数量越少,服务器压力就越小),此时我们可以将添加商品所导致的数据变动存储在 Cookie 内,然后等待下次发送请求时,一并发送给服务器处理。Cookie 的出现,为无状态的 HTTP 协议通信添加了状态。
Cookie 多数情况下都保存着用户的身份信息,因此对于 Cookie 的恶意攻击层出不穷。其本质上就是想要获得用户的 Cookie,再利用其中的身份信息伪装成用户获取相应资源,而浏览器的同源策略本质上就是保护用户的 Cookie 信息不会泄露。
WebSocket:让服务器也动起来
客户端无法获知请求的动态资源何时到位。有时候客户端想要请求的资源,服务器需要一定时间后才能返回(比如该资源依赖于其他服务器的计算返回结果),由于在 HTTP 协议下,网络通信是单向的,因此服务器并不具备当资源准备就绪时,通知浏览器的功能(要保障服务器的工作效率)。因此,基于 HTTP 协议通常的做法是,设置一个定时器,每隔一定时间由浏览器向服务器发送一次请求以探测资源是否到位。这种做法显然浪费了很多请求或者说带宽(每个请求都要携带 Cookie 和报头,这些都会占用带宽传输),低效且不够优雅。
我们希望当服务器资源到位时,能主动通知浏览器并返回相应资源。为了实现这一点,HTML5 标准推出了 WebSocket 协议,使浏览器和服务器实现了双向通信。除了 IE9 及以下的 IE 浏览器,所有的浏览器都支持 WebSocket 协议。
客户端告知服务端要升级为 WebSocket 协议的报头:
1 | GET /chat HTTP/1.1 |
服务端向客户端返回的响应报头:
1 | HTTP/1.1 101 Switching Protocols |
客户端如何发起 WebSocket 请求
像发起 AJAX 请求一样,发起 WebSocket 请求需要借助浏览器提供的 WebSocket
对象,该对象提供了用于创建和管理 WebSocket 连接,以及通过该连接收发数据的 API。所有的浏览器都默认提供了 WebSocket 对象。
和使用 XHRHttpRequest
对象一样,我们首先要实例化一个 WebSocket
对象:
1 | var ws = new WebSocket("wss://echo.websocket.org") |
传入的参数为响应 WebSocket 请求的地址。
与 AJAX 类似的是,WebSocket
对象也有一个 readyState
属性,用来表示对象实例当前所处的链接状态,有四个值:
- 0:表示正在连接中(CONNECTING);
- 1:表示连接成功,可以通信(OPEN);
- 2:表示连接正在关闭(CLOSING);
- 3:表示连接已经关闭或打开连接失败(CLOSED);
可以通过判断这个值来执行相应的代码。
除此之外,WebSocket
对象还提供一系列事件属性来控制连接过程中的通信行为:
onopen
:用于指定连接成功后的回调函数;onclose
:用于指定连接关闭后的回调函数;onmessage
:用于指定收到服务器数据后的回调函数;onerror
:用于指定报错时的回调函数;
通过.send()
方法,我们拥有了向服务器发送数据的能力(WebSocket 还允许我们发送二进制数据):
1 | ws.send('Hi, server!') |
WebSocket
对象的bufferedAmount
属性的返回值表示了还有多少字节的二进制数据没有发送出去,所以可以通过判断该值是否为 0 而确定数据是否发送结束。
1 | var data = new ArrayBuffer(1000000) |
WebSocket 是如何绕过浏览器的同源策略实现跨域资源共享,那就是当客户端与服务端创建 WebSocket 连接后,本身就可以天然的实现跨域资源共享,WebSocket 协议本身就不受浏览器同源策略的限制(同源策略只限制了跨域的 AJAX 请求)。
但如果没有浏览器同源策略的限制,那么用户的 Cookie 安全又由谁来保护呢?Cookie 的存在就是为了给无状态的 HTTP 协议通讯添加状态,因为 Cookie 是明文传输的,且通常包含用户的身份信息,所以非常受到网络攻击者的“关注”。但是想想 WebSocket 协议下的通讯机制,客户端和服务端一旦建立连接,就可以顺畅互发数据,因此 WebSocket 协议本身就是“有状态的”,不需要 Cookie 的帮忙,既然没有 Cookie,自然也不需要同源策略去保护,因此其实这个问题也不成立。
4. postMessage
JSONP,CORS 与 WebSocket 这些跨域技术都只适用于客户端请求异域服务端资源的情景。而有时候我们需要在异域的两个客户端之间共享数据,例如页面与内嵌 iframe 窗口通讯,页面与新打开异域页面通讯。
使用 postMessage
技术实现跨域的原理非常简单,一方面,主窗口通过 postMessage
API 向异域的窗口发送数据,另一方面我们在异域的页面脚本中始终监听 message
事件,当获取主窗口数据时处理数据或者以同样的方式返回数据从而实现跨窗口的异域通讯。
让我们用具体的业务场景与代码进一步说明,假如我们的页面现在有两个窗口,窗口 1 命名为“window_1”,窗口 2 命名为“window_2”,当然,窗口 1 与窗口 2 的“域”是不同的,我们的需求是由窗口 1 向窗口 2 发送数据,而当窗口 2 接收到数据时,将数据再返回给窗口 1。先让我们看看窗口 1script
标签内的代码:
1 | // window_1 域名为 http://winodow1.com:8080 |
可以看到,postMessage
函数接收两个参数,第一个为要发送的信息(可以是任何 JavaScript 类型数据,但部分浏览器只支持字符串格式),第二个为信息发送的目标地址。让我们再看看窗口 2script
标签内的代码:
1 | // window_2 域名为 http://window2.com:8080 |
我们在 window 上绑定了一个事件监听函数,监听message
事件。一旦我们接收到其他域通过postMessage
发送的信息,就会触发receiveMessage
回调函数。该函数会首先检查发送信息的域是否是我们想要的,如果验证成功则会向窗口 1 发送一条消息。
一方发送信息,一方捕捉信息。但是所有跨域技术都需要关注安全问题。postMessage 技术之所以能实现跨域资源共享,本质上依赖于客户端脚本设置了相应的message
监听事件。因此只要有消息通过postMessage
发送过来,我们的脚本都会接收并进行处理。由于任何域都可以通过postMessage
发送跨域信息,因此对于设置了事件监听器的页面来说,判断到达页面的信息是否是安全的是非常重要的事,因为我们并不想要执行有危险的数据。
那么如何鉴别发送至页面的信息呢?答案是通过 message
事件监听函数的事件对象,我们称它为event
,该对象有三个属性:
- data:值为其他 window 传递过来的对象;
- origin:值为消息发送方窗口的域名;
- source:值为对发送消息的窗口对象的引用;
应该着重检测event
对象的origin
属性,建立一个白名单对origin
属性进行检测通常是一个明智的做法。
最后,除了 IE8 以下的 IE 浏览器,所有的浏览器都支持 postMessage 方法!
5. proxy
Vue 中配置 proxy。vue 的转发机制 proxyTable,proxyTable 代理功能可以实现转发机制。
1 | // vue.config.js |
首先浏览器是禁止跨域的,但是服务端不禁止,在本地运行 npm run dev 等命令时实际上是用 node 运行了一个服务器,因此 proxyTable 实际上是将请求发给自己的服务器,再由服务器转发给后台服务器,做了一层代理。vue 的 proxyTable 用的是 http-proxy-middleware 中间件,因此不会出现跨域问题。
在请求跨域接口的时候就可以利用本地服务加上要跨域的接口地址即可。也就是说,代理服务器通过请求本地的服务器,然后本地的服务器再去请求远程的服务器。
1 | // 例如本地服务端口 localhost:8080 |