You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 父窗口想获取子窗口的DOM,因为跨源导致报错。document.getElementById("myIFrame").contentWindow.document// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
<iframeid="iframe"src="http://example.com/b.html"onload="test()"></iframe><scripttype="text/javascript">// function test(){// var iframe = document.getElementById('ifame');// var win = document.contentWindow;// undefined 可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的// var doc = win.document;//这里获取不到iframe里的document对象// var name = win.name;//这里同样获取不到window对象的name属性// console.log(win)// }document.domain='example.com';//设置成主域functiontest(){alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象}</script>
// b.html
<script>//监听a.html发来的消息window.onmessage=function(e){console.log(e.data)//给发送源回消息e.source.postMessage('nice to meet you',e.origin)}</script>
/* * A窗口的域名是<http://example.com:8080>,以下是A窗口的script标签下的代码: */varpopup=window.open(...popupdetails...);// 如果弹出框没有被阻止且加载完成// 这行语句没有发送信息出去,即使假设当前页面没有改变location(因为targetOrigin设置不对)popup.postMessage("The user is 'bob' and the password is 'secret'","https://secure.example.net");// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去(targetOrigin设置对了)popup.postMessage("hello there!","http://example.org");functionreceiveMessage(event){// 我们能相信信息的发送者吗? (也许这个发送者和我们最初打开的不是同一个页面).if(event.origin!=="http://example.org")return;// event.source 是我们通过window.open打开的弹出页面 popup// event.data 是 popup发送给当前页面的消息 "hi there yourself! the secret response is: rheeeeet!"}window.addEventListener("message",receiveMessage,false);
什么是跨域
浏览器的同源策略会导致跨域,这里同源策略又分为以下两种DOM同源策略:
即不能通过ajax的方法去请求不同源中的文档、浏览器中不同域的框架之间是不能进行js的交互操作的。
还有一点比较重要,为了安全,限制跨域是浏览器的行为,而不是JS/服务端的行为。你也可以自己开发一个浏览器,或者拿开源代码改,使得自己开发的浏览器能够跨域。
AJAX请求不同源的跨域
一、 JSONP(浏览器与服务端的通信)
为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP。该协议的一个要点就是通过创建一个 script 标签,将 src 设置为目标请求,插入到 dom 中,服务器接受该请求并返回数据,数据通常被包裹在回调钩子中。
直接打开 index.html 文件浏览器端会报错。就是同源限制造成的。
1. JSONP 的实现原理(利用script标签不受跨域限制而形成的一种方案)
JSONP 是 JSON with padding (填充式JSON或参数式JSON)的简写,是应用JSON的一种方式。
JSONP的实现原理很简单,利用
<script>
标签没有同源限制的特点,也就是<script>
的src链接可以访问不同源的。不受同源限制的还有<img>
、<iframe>
、<link>
,对应这两个标签实现的跨域方法也有,比如图片ping等。(图片ping:可以访问任何url,一般用来进行点击追踪,做页面分析常用的方法。缺点:不能访问响应文本,只能监听是否响应)下面为允许跨域资源嵌入的示例,即一些不受同源策略影响的标签示例:
<script src="..."></script>
标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。<link rel="stylesheet" href="...">
标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari 和 Opera。<img>
嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG<video>
和<audio>
嵌入多媒体资源。<object>
,<embed>
和<applet>
的插件。@font-face
引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。<frame>
和<iframe>
载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。我们访问服务端,一般是获取存JSON数据,而JSONP则返回的是,包含函数的数据,将我们需要的 JSON 数据作为函数的参数。
即在客户端一般通过
<script>
标签的 src 访问带有 callback 查询参数的请求,来获取返回带有函数的数据,然后执行它,即可拿到这份数据(服务端注入的数据)完成跨域访问。由于使用 script 标签的 src 属性,因此只支持get方法。
JSONP的请求过程:
script
标签,并给其src
赋值(同时在客户端注册一个cb
方法jsonpCallback()
,放到window
对象上;并把callback
的名字(jsonpCallback
)传给服务器:类似http://example.com/api/?cb=jsonpCallback
),再将script
标签插入 DOM。script
的src
赋值并插入 DOM 后(不将script
标签插入 DOM ,是不会发起请求的),浏览器就会发起一个请求。"jsonpCallback({name: 'abc'})"
返回。当浏览器接收到了响应数据,由于发起请求的是script
,所以相当于直接调用jsonpCallback
方法,并且传入了一个参数。(解析script
标签后,会执行jsonpCallback(JOSN)
)。通过这种方式,即可实现跨域获取数据。
2. 封装一个jsonp工具
测试:node server.js 打开index.js 可以看到跨域成功
3. 注意
二、CORS(通过前后端http header配置来进行跨域的一种方式)
Cross-Origin Resource Sharing 跨源资源共享
浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
可以把CORS分为:简单请求、复杂请求。
1. 简单请求
流程:
Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com
: 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。Access-Control-Allow-Credentials: true
:该字段可选。它的值是一个布尔值且只有 true 这一个值,表示是否允许发送Cookie,同时开发者必须在AJAX请求中打开withCredentials: true
属性才生效。默认情况下,Cookie不包括在CORS请求之中。2. 非简单请求
2.1 预检请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,"预检"请求的头信息包括两个特殊字段。
Access-Control-Request-Method
:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,例如PUT。Access-Control-Request-Headers
:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,例如X-Custom-Header。2.2 预检请求的回应
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
Access-Control-Allow-Origin
字段,表示该域名可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。
Access-Control-Allow-Methods
: 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。Access-Control-Allow-Headers
:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。Access-Control-Allow-Credentials
:该字段与简单请求时的含义相同。Access-Control-Max-Age
:该字段可选,用来指定本次预检请求的有效期,单位为秒。例如:有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。2.3 浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个
Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。优点
缺点
使用方式
CORS和JSONP对比
完全不同源的跨域(两个页面之间的通信)
二、 postMessage
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是 iframe 窗口和window.open方法打开的窗口,它们与父窗口无法通信。
比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。
反之亦然,子窗口获取主窗口的DOM也会报错。
如果两个窗口一级域名相同,只是二级域名不同,那么设置
document.domain
属性,就可以规避同源政策,拿到DOM。对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。
1 通过修改document.domain来跨子域(只适用于主域相同、不同子域的框架间的交互)
有一个页面,它的地址是
http://www.example.com/a.html
, 在这个页面里面有一个 iframe,它的src是http://example.com/b.html
, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的。http://www.example.com/a.html
中设置document.domain
:http://example.com/b.html
中也设置document.domain
:2 利用 location.hash + iframe(这种跨域方法主要是通过设置/监听url的hash部分,来实现跨域)
原理:location.hash方式跨域,是子框架能够修改父框架src的hash值,子框架通过修改父框架 src 的 hash 值来传递信息。且更改hash值,页面不会刷新。但是传递的数据的字节数是有限的。
步骤:动态插入一个iframe,将iframe的src属性指向服务端地址。这时top window和包裹这个iframe的子窗口是不能通信的(同源策略),所以改变子窗口的路径就行了,将数据当做改变后的路径的hash值加在路径上,然后就能通信了(和window.name跨域几乎相同),将数据加在index页面地址的hash值上。index页面监听地址的hash值变化(html5有hashchange事件,用setInterval不断轮询判断兼容ie6/7),然后做出判断,处理数据。
假设域名a.com下的文件cs1.html要和jianshu.com域名下的cs2.html传递信息。
1、cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向jianshu.com域名下的cs2.html页面。
2、cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据。
3、同时在cs1.html上加一个定时器,隔一段时间来判断 location.hash的值有没有变化,一旦有变化则获取获取hash值。
注:由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe。
优点:1.可以解决域名完全不同的跨域。2.可以实现双向通讯。
缺点:location.hash会直接暴露在URL里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验。另外由于URL大小的限制,支持传递的数据量也不大。有些浏览器不支持 onhashchange 事件,需要轮询来获知URL的变化。
3 利用 window.name + iframe (父框架和子框架的src必须指向同一域名)
原理:window.name(一般在js代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,每个 iframe 都有 window.name 的属性。window.name属性的神奇之处在于一个 iframe 的 name 值在不同的页面(甚至不同域名,即改变了这个iframe的 src 默认情况下,name依然保持不变)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。
步骤:在iframe载入过程中,迅速重置iframe.src的指向,使之与index.html同源,那么index页面就能去获取到这个 iframe 的name值了(把子框架的 src 改变和父框架同源,而子框架的 name 依然保持不变)!
所以,iframe子框架需要一直不停地刷新,每次触发onload事件后,重置src,相当于重新载入页面,又触发onload事件,于是就不停地刷新了(但是需要的数据还是能输出的);
优点:window.name容量很大,可以放置非常长的字符串;(2MB)
缺点:必须监听子窗口window.name属性的变化,影响网页性能。
4 window.postMessage
信息传递除了客户端与服务器之前的传递,还存在以下几个问题:
上面两种方法都属于破解,再加上iframe用的比较少了,所以这些方法也就有点过时了。window.postMessage是一个HTML5的api,允许两个窗口之间进行跨域发送消息,不论这两个窗口是否同源。这个应该就是以后解决dom跨域通用方法了。
父窗口和子窗口都可以通过message事件,监听对方的消息。message事件的事件对象event,提供以下三个属性:
优点:不需要后端介入就可以非常简单的的做到跨域,一个函数外加两个参数(请求url,发送数据)就可以搞定;移动端兼容性好;
缺点:
LocalStorage
文章开头说了,浏览器的同源策略会导致,LocalStorage有同源限制。
解决办法:通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。
References
动手实现一个JSONP
前端跨域问题及解决方案
利用window.name+iframe跨域获取数据详解
跨域资源共享 CORS 详解
The text was updated successfully, but these errors were encountered: