什么是跨域
作为前端开发,尤其当下前后端分离越来越普遍,跨域成为我们工作中经常会遇到的问题。
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript
实施的安全限制。所谓的同源是指 域名(包括二级域名)、协议、端口均相同。
localhost
调用127.0.0.1
也属于跨域。
同源策略限制了以下行为:
- 无法读取
Cookie
、LocalStorage
、SessionStorage
和IndexDB
- 无法获取
DOM
和JS
对象 - 无法发送
Ajax
请求
常见跨域场景主有二种:
- 场景一:嵌套第三方页面,并互相通信。
- 场景二:跨域
Ajax
请求。
场景一主要涉及的前端技术有:
Iframe
…PostMessage
- 代理服务(
Nginx
)
场景二主要涉及的前端技术有:
JSONP
- 跨域资源共享(
Cors
) - 代理服务(
Nginx
)
接下来,我们分别探讨一下。
Iframe
Iframe
的应用一直非常广泛,包括当下。用于在你的页面开辟一个子窗体嵌套展示其他页面。主要解决页面级别的复用问题。很多时候,你不仅需要展示子页面,还需要和子页面沟通。由于Iframe
同样受同源策略限制,跨域问题就随之而来。
曾经我们主要通过以下2种方式解决iframe
跨域问题:
- 设置
domain
解决主域名相同,二级域名不同导致的跨域阻碍。 - 通过一个同域的中间页。
当下,我们主要通过接下来要介绍的 PostMessage
来解决跨域窗口通信问题,以上方式就不具体赘述。
PostMessage
window.postMessage
是一个安全的、基于事件的消息API
。它允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文档、多窗口、跨域消息传递。IE8+
,chrome
,firefox
等都已经支持。同源窗口通信也可以使用PostMessage
,逻辑比较清晰。
PostMessage
实现通信的方式也很简单,分为两步:
- 发送消息
- 接受消息
发送消息
在需要发送消息的源窗口调用targetWindow.postMessage(message,targetOrigin)
方法即可发送消息:
targetWindow
targetWindow
是对目标窗体的引用。获得该引用的方法包括:
Window.open
JS打开的窗体Window.opener
打开当前窗体的窗体HTMLIFrameElement.contentWindow
iframe窗体Window.parent
当前窗体的父窗体Window.frames[index]
当前窗体的 iframe
message
参数
- 作用:要传递的数据。
- 类型:可以是
JS
的任意基本类型或可复制的对象。然而部分浏览器只能处理字符串参数,保险起见,推荐使用JSON.stringify()
方法对数据序列化。
targetOrigin
参数:
- 类型:
string
- 作用:为了安全考虑,指明目标窗口的源,协议+域名+端口号[+path],path会被忽略,所以可以不写。当然如果愿意也可以设置为
"*"
,这样可以传递给任意窗口,如果要指定和当前窗口同源的话可设置为"/"
。
只有当目标窗口的源与postMessage函数中传入的源参数值匹配时,才能接收到消息。
举个栗子:
// http://www.domainA.com
// 发送消息
var iframe = document.getElementById('iframe')
iframe.contentWindow.postMessage('hi', 'http://www.domainB.com')
接收消息
目标窗体通过监听window
的message
事件就可以接收任何窗口传递来的消息了。
message
事件的event
对象有三个属性,分别是:
event.data
表示接收到的消息;event.origin
表示postMessage
的发送来源,包括协议,域名和端口;event.source
表示发送消息的窗口对象的引用。我们可以用这个引用来建立两个不同来源的窗口之间的双向通信。
举个栗子:
// http://www.domainB.com
// 接受消息
function receiveMsg(event) {
// 打印消息
console.log(event.data)
// 双向通信
event.source.postMessage('hello', 'http://www.domainA.com')
// 如果是父窗口 也可以这么沟通
window.parent.postMessage('hello', 'http://www.domainA.com')
}
// 监听message事件
if (window.addEventListener) {
window.addEventListener('message', receiveMsg, false);
}else {
window.attachEvent('message', receiveMsg);
}
JSONP
曾经主流的跨域Ajax
请求的主要方式,虽然当下已经有点过时,但还是值得了解以下的。它的原理比较有趣,算是一种“投机取巧”的设计模式吧。
- 原理:利用
html
页面允许通过相应的标签从不同域名下加载静态资源文件是被浏览器允许的特点,动态的创建script
标签,再去请求一个带参(包含一个回调方法作为参数)url
来实现跨域通信。 - 缺点:只能够实现
get
请求。
//原生实现方式
let script = document.createElement('script');
script.src = 'http://www.nealyang.cn/login?username=Nealyang&callback=callback';
document.body.appendChild(script);
function callback(res) {
console.log(res);
}
跨域资源共享(Cors)
CORS
是目前主流的跨域 Ajax
请求解决方案。
- 是一个
W3C
标准,全称是”跨域资源共享”(Cross-origin resource sharing
)。 - 它允许浏览器向跨源服务器,发出
XMLHttpRequest
请求,从而克服了AJAX
只能同源使用的限制。 - 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,
IE
浏览器不能低于IE10
。 - 整个
CORS
通信过程,都是浏览器自动完成,不需要用户参与。 - 实现
CORS
通信的关键是服务器。只要服务器实现了CORS
接口,就可以跨源通信。浏览器一旦发现AJAX
请求跨源,就会自动添加一些附加的头信息,有时还会多一次附加的请求,但用户不会有感觉。
要理解Cors
需要搞清楚以下几个概念:
简单请求
简单请求需要满足以下几点:
- 请求方式为 HEAD、POST 或者 GET
- http头信息不超出以下原始字段:Accept、Accept-Language 、 Content-Language、 Last-Event-ID、 Content-Type。
- Content-Type限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain
非简单请求
简单请求之外的其他请求。非简单请求在正式通信之前,浏览器会先发送OPTION请求,进行预检,这一次的请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
withCredentials
属性
CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。另一方面,开发者必须在AJAX请求中打开withCredentials属性。
代理服务(如 Nginx)
- 特殊,个别,紧急情况下,可以考虑通过代理服务(如
Nginx
)配置url
地址映射解决跨域等问题。 - 一般由运维/后端人员负责配置。
- 不用发布代码,高效,非常有用。